App 108868304 الخادم المحرك الصفحات

الربط التقني مع سلة — ملف الاجتماع

كل رقم في هذه الصفحة مقروء من الكود أو من الخادم الحيّ، لا مقدَّر. والمؤشّرات في الأعلى تفحص الخدمة الآن وأنت تقرأ. انتقل بين التبويبات الأربعة — وتبويب «وين ألقى كل شيء» فيه مكان كل شيء قد يُسأل عنه ورابطه المباشر.

كيف يعمل — من التاجر إلى الزائر

أربع خطوات لا خامسة لها. ولا خطوة منها تكتب حرفاً في وصف المنتج ولا في قالب المتجر.

1
التاجر يبني
من لوحة مدمجة داخل سلة — بالنقر، ومعاينة حيّة قبل النشر.
2
نخزّن عندنا
JSON في قاعدة بيانات معزولة بـstore_id، وصور في تخزين منفصل.
3
المحرك يرسم
سنِبِت صغير في المتجر يسحب المحرك، فيجلب البلوكات ويرسمها وقت التشغيل.
4
الزائر يرى
تحت وصف المنتج — بألوان القالب وخطّه. وإيقاف التطبيق يمحو الأثر كاملاً.

الحالة الحيّة

تُفحص عند فتح الصفحة وتُحدَّث كل ٦٠ ثانية. لو كانت كلها خضراء فالخدمة تعمل الآن.

الخادم — يُفحص
المحرك — الحجم المخدوم فعلاً
الصفحات القانونية الثلاث
زمن استجابة الخادم
وعن كلاود فلير

لا تحتاج تسجيل الدخول أثناء الاجتماع. كل ما ستعرضه روابط عامة وصفحات داخل سلة. والدخول يلزم فقط لو طلبوا استعلاماً حيّاً من قاعدة البيانات — وطريقته في تبويب «وين ألقى كل شيء».


التطبيق بلغة بسيطة

ابدأ الاجتماع بهذا. الألم أولاً، ثم الحل — لا بقائمة مزايا.

المشكلة

وصف المنتج في سلة نص خام. والتاجر الذي يريد عرض ضماناته أو الإجابة عن أسئلة متكررة أو وضع شهادات عملاء لا يملك إلا فقرة طويلة يقرؤها قليل. وسلة أزالت محرر HTML من الوصف، فحتى لصق كود لم يعد ممكناً.

فالزائر يصل إلى صفحة دفع التاجر ثمن وصوله، ثم يخرج لأن الصفحة لم تجب عن سؤاله.

الحل — ١٨ بلوكاً بالنقر

المجموعةالبلوكات
إقناعالضمانات · صندوق الثقة · خطوات الاستلام · السعر والخصم · تأطير السعر · جدول المواصفات · شهادات العملاء · الأسئلة الشائعة
إجراءزر الإضافة للسلة · زر برابط · زر واتساب · عدّاد تنازلي
وسائطصورة · فيديو · مقارنة قبل/بعد
محتوىنص · فاصل · شارة

ثلاث حقائق قلها بنفسك

لا نلمس وصف المنتج

البلوكات تُخزَّن عندنا وتُرسم وقت التشغيل. إيقاف التطبيق يعيد كل شيء كما كان بلا أثر.

لا صلاحية كتابة واحدة

قراءة فقط، ولا نصل لعملاء التاجر ولا طلباته ولا مدفوعاته.

لا نتتبّع الزوار

لا كوكيز ولا تحليلات ولا تخزين لأي بيانات عن زوار المتجر.


الأرقام

كلها مقروءة من الكود أو من قاعدة البيانات الحيّة — ولا واحد منها تقدير.

١٨
بلوكاً جاهزاً
٨٥٤
أيقونة من مكتبة سلة
١١
حدثاً نستقبله ونعالجه
٢
صلاحية — كلتاهما قراءة
٥ جيجا
و١٠٠٠ صورة لكل متجر
٤٥ ك.ب
حجم المحرك كاملاً
١١٩٤
حرفاً — المحمِّل المسجَّل في سلة
٤٩ ر.س
شهرياً · ٧ أيام تجربة

الأربعة التي قالوا إنهم سيراجعونها

رسالتهم حرفياً: «الصلاحيات، الأحداث، نمط المصادقة، رابط استقبال التنبيهات». هذا التبويب هو جوهر الاجتماع.

1
الصلاحيات
اثنتان، قراءة فقط. ولا نملك صلاحية كتابة واحدة.
2
الأحداث
أحد عشر حدثاً، كلها app.*. ولا حدث متجر واحد.
3
المصادقة
Easy Mode — التوكن يصل بالويبهوك، بلا صفحة callback عندنا.
4
التنبيهات
نقطة واحدة، توقيع HMAC-SHA256، ولا fail-open.

١ · الصلاحيات

اثنتان فقط وكلتاهما قراءة. وننادي نقطتين لا غير في واجهة سلة كلها.

الصلاحيةالنوعلماذا نحتاجها
settingsreadلمعرفة معرّف المتجر فقط
productsreadلعرض قائمة منتجات التاجر في المنتقي

وكل ما نناديه من واجهة سلة

النقطةمتىلماذا
GET /store/infoمرة واحدة عند التثبيتلربط معرّف التاجر بمعرّف المتجر
GET /productsعند فتح منتقي المنتجاتلعرض قائمة منتجات التاجر
◆ قلها هكذا

طلبنا أقل ما يلزم. لا نستطيع تعديل أي شيء في متجر التاجر حتى لو أردنا، ولا نرى طلباته ولا عملاءه.


٢ · الأحداث

أحد عشر حدثاً، كلها app.* تصل تلقائياً. ولم نشترك في أي حدث متجر — لا order.* ولا product.* ولا غيرها.

الحدثالمجموعةما نفعله عنده
app.store.authorizeتثبيتيصل التوكن — نخزّنه ونربط معرّف التاجر بمعرّف المتجر
app.installedتثبيتنسجّل التثبيت ونستقبل الإعدادات الأولى
app.uninstalledتثبيتنمحو التوكنات فوراً ونعلّم المتجر مُلغى — ولا نحذف بياناته
app.settings.updatedتثبيتنحدّث إعدادات التطبيق عند التاجر
app.trial.startedاشتراكنفتح الخدمة
app.trial.expiredاشتراكنغلق الخدمة
app.trial.canceledاشتراكنغلق الخدمة
app.subscription.startedاشتراكنفتح الخدمة
app.subscription.renewedاشتراكنمدّد الصلاحية
app.subscription.expiredاشتراكنغلق الخدمة — والبيانات تبقى
app.subscription.canceledاشتراكنغلق الخدمة — والبيانات تبقى
◆ ولو سألوا: لماذا تعالجون أحداث الاشتراك؟

لأن سلة تحصّل وتخبرنا، ونحن نوقف الخدمة عند انتهاء التجربة أو الاشتراك. بدونها يستمر التاجر بلا اشتراك.


٣ · نمط المصادقة — Easy Mode

بلا صفحة callback عندنا. التوكن يصل موقَّعاً في حدث app.store.authorize، فلا يمرّ في متصفح أحد.

النمطEasy Mode
redirect_urlshttps://accounts.salla.sa/callback/108868304

وما نفعله بالتوكن

البندالتفصيل
التخزينصفّ مستقل لكل متجر في قاعدة البيانات
التجديدتلقائي قبل انتهائه بدقيقتين
التزامنقفل ذرّي يمنع تجديدين معاً — لأن refresh_token في سلة أحادي الاستخدام، والتجديد المزدوج يُبطل الجلسة
الإلغاءيُمحى فوراً عند app.uninstalled

وجلسة اللوحة — ولماذا لا نستعمل توكن سلة مباشرة

توكن سلة المدمج عمره دقائق، وتجديده يعيد رسم الإطار فيفقد التاجر مكانه في العمل. فنستعمله مرة واحدة لإثبات الهوية، ثم نصدر جلستنا.

١٢ ساعة
عمر الجلسة الواحدة
٧ أيام
سقف مطلق ثم إعادة إثبات الهوية من سلة
١
متجر واحد لكل جلسة — موقّعة ومرتبطة به

٤ · رابط استقبال التنبيهات

نقطة واحدة تستقبل الأحداث الأحد عشر. وكل طلب يُرفض ما لم يحمل توقيعاً صحيحاً.

الرابطhttps://product-blocks-api.pomstore.workers.dev/v1/webhook
الاستراتيجيةSignature — HMAC-SHA256

كيف نتحقق

  • نقرأ الجسم الخام مرة واحدة ولا نعيد بناءه — أي فرق مسافة يكسر التوقيع
  • HMAC-SHA256 بالسر المشترك، ومقارنة ثابتة الزمن
  • لا fail-open — أي توقيع غير مطابق ⇐ 401 ورفض
✓ أثبتها أمامهم — انسخ الأمر والصقه في الطرفية
أمر الإثباتcurl -i -X POST https://product-blocks-api.pomstore.workers.dev/v1/webhook -d '{}'

ولو فتحته في المتصفح مباشرة يرجع 405 method not allowed لأن الويبهوك POST فقط. وكلاهما رفض، لكن الـ401 هو إثبات التوقيع.

والتكرار والفشل

الحالةما نفعله
منع التكرارمفتاح من بصمة الجسم الخام — لا من created_at الذي قد يغيب
آلة الحالاتprocessing → ok / failedوالفاشل يحتفظ بحمولته لإعادة التشغيل
خطأ عابر500 لتعيد سلة الإرسال
حمولة فاسدة200 فلا تُعاد بلا فائدة
الكنسمهمة يومية ٣:٠٠ UTC — الناجح يُمسح بعد ٧ أيام والفاشل بعد ٩٠

السنِبِت — من أين يأتي وما فيه

المسجَّل في بورتال سلة ليس المحرك — بل محمِّل صغير لا يفعل إلا ثلاثة أشياء. وهذا سؤال سيأتي، فاعرضه أنت.

الحقلالقيمة
المكانبوابة الشركاء ← التطبيق 108868304App Snippets
placebefore
tagbody
الحقنتلقائي بعد تثبيت التطبيق — تستضيفه سلة نفسها على salla-dev-portal.s3
الحجم١١٩٤ حرفاً من أصل ٥٠٬٠٠٠ مسموحة

وهذا كامله حرفياً

(function () {
  var URL = 'https://product-blocks-panel.pages.dev/engine/pb-engine.js';

  // حارس التكرار: التنقّل الداخلي في المتجر قد يعيد تنفيذ السنِبِت،
  // وتحميل المحرك مرتين يعني مراقبَين ورسماً مزدوجاً.
  if (document.querySelector('script[data-pb-loader]')) return;

  var s = document.createElement('script');
  s.src   = URL;
  s.async = true;
  s.setAttribute('data-pb-loader', '1');
  document.head.appendChild(s);
})();

والمحرك الذي يسحبه

الرابطhttps://product-blocks-panel.pages.dev/engine/pb-engine.jsفتح
الحجم45,561 bytes (~45 KB) · يُخدَّم مضغوطاً من CDN · كاش ٥ دقائق
المصدرdashboard/src/engine/index.js

وما يفعله في صفحة المنتج

  1. ينتظر تهيئة Salla SDK (حتى ١٠ ثوانٍ) ليقرأ store.id و product.id
  2. يجلب GET /v1/blocks ويحفظ الرد في ذاكرة الجلسة
  3. يبحث عن نقطة التثبيت بعدة محدّدات بالترتيب — ولو لم يجدها لا يرسم شيئاً
  4. يرسم البلوكات، ويراقب document.body لإعادة الرسم بعد التنقّل الداخلي
  5. لا يحمّل خط الأيقونات إلا إن كان في البلوكات أيقونة فعلاً
ولماذا محمِّل لا المحرك كاملاً؟

ثلاثة أسباب — وبموافقة سلة الصريحة المكتوبة. التفصيل في تبويب «الأسئلة والعرض» ← البند الحسّاس الثاني.


كيف تظهر البلوكات في صفحة المنتج

هذا أدقّ سؤال تقني سيأتي، فاعرضه أنت. البلوكات مخزَّنة عندنا لا عند سلة — فكيف تصل إلى الصفحة، وكيف يُحدَّد مكانها، وماذا يحدث لو تغيّر القالب.

1
سلة تحقن السنِبِت
تلقائياً قبل </body> بعد التثبيت. نحن لا نحقن شيئاً بأنفسنا.
2
المحمِّل يجلب المحرك
وسم script async واحد. وينتظر تهيئة Salla SDK حتى ١٠ ثوانٍ.
3
المحرك يقرأ ويجلب
معرّف المتجر والمنتج من SDK والرابط، ثم GET /v1/blocks.
4
يبني عنصراً جديداً
div مستقل يضعه جاراً لعنصر الوصف — لا داخله.

١) كيف يعرف أنها صفحة منتج — وأي منتج

ما يحتاجهمن أين يأخذهلماذا بهذا الترتيب
هل هي صفحة منتج/p{رقم} في الرابط
أو page.slug === 'product.single'
شرطان، يكفي أحدهما — فلا نعتمد على مصدر واحد
معرّف المنتجالرابط أولاً: /p(\d+)
ثم salla.config page.id
الرابط مقدَّم عمداً: بعد التنقّل الداخلي يتأخر salla.config عن الواقع لحظات، أما الرابط فيتغيّر فوراً
معرّف المتجرsalla.config.get('store.id')من سلة نفسها — لا من مُدخل ولا من الرابط

٢) كيف يحدّد مكان الظهور

يجرّب أربعة محدّدات بالترتيب ويقف عند أول ما يجده. والترتيب مقصود: الأول رسمي من سلة، والبواقي أسماء شائعة في الثيمات.

الترتيبالمحدّدمصدره
١[data-testid="store-product-description"]سمة رسمية من سلة — الأثبت
٢.product__descriptionتسمية BEM شائعة في الثيمات
٣.product-descriptionتسمية بديلة شائعة
٤.article--mainاحتياطي أخير
✓ ولو لم يجد أياً منها

لا يرسم شيئاً وينصرف بهدوء. لا خطأ في الكونسول ولا عنصر ناقص ولا تخطيط مكسور — الصفحة تبقى كما لو أن التطبيق غير مثبَّت. بلوك غائب أهون من صفحة مكسورة.

٣) وكيف يضعها بالضبط

التاجر يختار الموضع من إعدادات التطبيق، وكل خيار عملية DOM واحدة:

الخيارالعمليةالأثر على عنصر الوصف
فوق الوصفinsertBefore(root, host)لا يُمسّ
تحت الوصف
الافتراضي
insertBefore(root, host.nextSibling)لا يُمسّ
بدل الوصفhost.style.display = 'none'
+ insertBefore بعده
يُخفى بـCSS — ولا يُحذف
✓ ثلاث نقاط قلها هنا حرفياً

١ · لا نكتب داخل أي عنصر من عناصر الثيم. نبني div جديداً ونضعه جاراً لعنصر الوصف. ولا نستعمل innerHTML على شيء يملكه القالب.

٢ · حتى «بدل الوصف» لا يحذف الوصف — يضع عليه display:none وسمة data-pb-hidden. فالتراجع فوري وكامل.

٣ · كل ما نضيفه موسوم بـdata-pb — فالتنظيف يعرف ما يزيله بدقة، ولا يلمس شيئاً آخر في الصفحة.

٤) لماذا مراقِب لا حدث تنقّل

هذه مشكلة حقيقية واجهناها وحللناها. سلة تبدّل محتوى الصفحة بلا إعادة تحميل، فيُمسح ما رسمناه، وonReady لا يُطلق ثانيةً. ولا يوجد حدث تنقّل موثَّق في Twilight — راجعنا الدوكس.

فالحل ألّا نعتمد على حدث أصلاً:

الآليةالتفصيل
MutationObserverعلى document.body بـchildList + subtree، مؤجَّل ٢٢٠ مللي فلا يعمل مع كل تغيّر صغير
popstateزر رجوع المتصفح لا يُطلق تغييراً في DOM دائماً
pageshowالرجوع من تبويب آخر بعد أن أفرغ المتصفح الصفحة من الذاكرة
وفحص رخيص قبل كل رسمهل هي صفحة منتج؟ وهل جذرنا موجود؟ وهل data-pb-product يطابق المنتج الحالي؟ — وإلا لا نفعل شيئاً
⚠ وهنا بند في إرشاداتكم سيُذكر — اذكره أنت أولاً

في إرشادات سلة: «لا تستخدم body كمرجع مباشر». ونحن نراقب document.body. سألنا دعمكم عن هذا صراحةً، وجاء الجواب (٥ أغسطس ٢٠٢٦):

◆ جواب الدعم — حرفياً

«لا توجد طريقة محددة يجب اتباعها في هذه الحالة، ويمكنكم استخدام أي نهج ترونه مناسبًا، بشرط ألا يخالف إرشادات سلة الخاصة بتطوير التطبيقات.»

وما تقوله بعده:

القاعدة في إرشاداتكم واردة في سياق innerHTML تحديداً — «استعمل innerHTML بمسؤولية، ولا تستخدم body كمرجع مباشر». أي المقصود الكتابة على الجسم، لا مراقبته.

ونحن نراقب ولا نكتب. لا نلمس body.innerHTML ولا نضيف إليه شيئاً — بلوكاتنا تُحقن في حاوية الوصف وحدها.

ولو كانت قراءتنا خاطئة عندكم، قولوها ونغيّرها. لكن لا يوجد حدث تنقّل موثَّق في Twilight، فنحتاج بديلاً تقترحونه.

٥) وماذا يحدث في الحالات الصعبة

الحالةالسلوك
لم نجد نقطة التثبيتلا نرسم شيئاً والصفحة كما هي
الخادم لا يستجيبننظّف ونخرج — ولا نترك بلوكات منتج سابق معروضة
الزائر انتقل أثناء انتظار الشبكةنفحص المنتج ثانيةً بعد وصول الرد ونلغي الرسم إن تغيّر
عمليتا رسم متزامنتانقفل mounting يمنع التداخل
المحرك وصل قبل SDK سلةينتظر حتى ١٠ ثوانٍ بمؤقّت يوقف نفسه — لا setInterval
التطبيق معطَّل أو بلا بلوكاتننظّف ونخرج فيختفي كل أثر فوراً
بلوك واحد فشل رسمهالبواقي تُرسم، ونسجّل تحذيراً واحداً فقط

٦) وما نضيفه للصفحة — بالضبط

ما نضيفهمتىملاحظة
script المحمِّلدائماً١١٩٤ حرفاً — تحقنه سلة
script المحركدائماً~٤٥ ك.ب مضغوطاً من CDN
style#pb-styleعند أول رسممرة واحدة، مضمَّن، لا طلب شبكة
link#pb-siconsشرطياً فقطخط أيقونات سلة ٣١٢ ك.ب — لا يُحمَّل إلا إن استعمل التاجر أيقونة فعلاً
div.pb-rootعند الرسمعنصر واحد يحمل كل البلوكات، بـfont-family: var(--font-main)
ونقطة تستحق أن تُقال

خط الأيقونات كان يُحقن دائماً — ٣١٢ ك.ب على كل صفحة منتج ولو لم تحمل البلوكات أيقونة واحدة. جعلناه شرطياً لأن دوكسكم تقيس أداء صفحة الزائر عند المراجعة. هذا مثال على أننا قرأنا متطلباتكم لا أننا بنينا ثم سألنا.


المعمارية وعزل المتاجر

كل شيء على Cloudflare — لا مزوّد آخر ولا خادم ثالث. والعزل في بنية التخزين نفسها لا في شرط برمجي يمكن نسيانه.

لوحة التاجر (مدمجة في سلة)
      │  توكن سلة ← مرة واحدة ← جلسة موقّعة منّا
      ▼
┌────────────────────────────────────────────┐
│    Cloudflare Worker  +  D1  +  R2         │
└────────────────────────────────────────────┘
      ▲  POST /v1/webhook   ← أحداث سلة (تحقق HMAC)
      │  GET  /v1/blocks    → { settings, blocks }
      ▼
سنِبِت المتجر (محمِّل) ──→ المحرك من Cloudflare Pages
      ▼
صفحة المنتج عند الزائر
Workers

الخادم — الويبهوك والواجهة الإدارية والمهمة اليومية

D1

إعدادات البلوكات وتوكنات المتاجر وسجل الأحداث

R2

صور التجّار — تُخدَّم عبر الـWorker لا برابط مباشر

Pages

لوحة التحكم + ملف المحرك + الصفحات القانونية

عزل المتاجر

  • كل استعلام قاعدة بيانات يحمل WHERE store_id = ?
  • كل ملف في التخزين تحت بادئة {store_id}/
  • هوية المتجر تأتي من توكن سلة أو من الويبهوك — لا من مُدخل مستخدم

مراجعة أمنية داخلية فحصت هذا البند خصيصاً ولم تجد أي مسار لبيانات متجر آخر.

وين ألقى كل شيء

لو سُئلت عن أي شيء — أين مخزَّن، من أين يُضبط، كيف أُريه — فالجواب هنا بمكانه ورابطه المباشر. الروابط تفتح في تبويب جديد، وأزرار النسخ تنسخ القيمة كما هي.

1
بوابة سلة
الصلاحيات · الأحداث · المصادقة · السنِبِت · التسعير · ملف النشر
2
كلاود فلير
الخادم · قاعدة البيانات · الصور · اللوحة · الأسرار · السجلات
3
المتجر التجريبي
اللوحة المدمجة · صفحة المنتج كما يراها الزائر
4
الكود والوثائق
كل ملف ودوره، ومكان كل وثيقة على جهازك

١ · في بوابة الشركاء

كل ما يخص إعدادات التطبيق عند سلة. افتح صفحة التطبيق أولاً، ثم تنقّل بين تبويباتها — الجدول يذكر اسم التبويب لكل بند.

صفحة التطبيقhttps://portal.salla.partners/apps/108868304فتح
لو سألوك عنالتبويب في صفحة التطبيقالقيمة عندنا
الصلاحياتالصلاحيات / Scopessettings:read · products:read — لا غير
الأحداث والويبهوكالويبهوكس / Webhooksنقطة واحدة · استراتيجية Signature · ١١ حدثاً
نمط المصادقةالإعدادات / OAuthEasy Mode + redirect_urls
Client IDالإعدادات4c335b9e-205d-4bd1-b11b-2d1769f35957
السنِبِتApp Snippetsمحمِّل ١١٩٤ حرفاً · place: before · tag: body
الصفحة المدمجةEmbedded Pagesالمسار blocks-builder
التسعير والباقاتالباقات / Plans٤٩ ر.س شهرياً · تجربة ٧ أيام
ملف النشرالنشر / Publish٤ من ٥ أقسام مكتملة — الناقص: الفيديو
الاسم والوصف واللقطاتصفحة التطبيق / Page Builder«وصّاف يوصف منتجك عنك»
⚠ لا تفتح على الشاشة

أي شاشة تعرض client_secret أو webhook_secret. لا أحد سيطلبهما — وسلة تملكهما أصلاً. وإن احتجت فتح تبويب الإعدادات، مرّر بسرعة أو شارك نافذة بعينها لا الشاشة كاملة.


٢ · في كلاود فلير

البنية كلها في حساب واحد. لن تحتاجها في الاجتماع — لكن لو طُلب إثبات أن البيانات معزولة أو أن الخادم يعمل، فهذه أسرع طريق.

الحسابzegamer6669@gmail.com
Account IDec951faa2dba236513cd3795c06833c5
اللوحةhttps://dash.cloudflare.com/ec951faa2dba236513cd3795c06833c5فتح
لو سألوك عنالقسم في اللوحةالاسم عندنا
الخادمWorkers & Pagesproduct-blocks-api
قاعدة البياناتStorage → D1product-blocks-db
الصورR2 Object Storageproduct-blocks-media
اللوحة والمحركWorkers & Pagesproduct-blocks-panel
الأسرارالخادم → Settings → VariablesSALLA_WEBHOOK_SECRET · SALLA_CLIENT_SECRET · ADMIN_KEY
المهمة اليوميةالخادم → Settings → Triggers0 3 * * * — حذف وسائط المغادرين وكنس السجل
السجلات الحيّةالخادم → Logsأو بالأمر wrangler tail أدناه

وأين تُخزَّن كل بيانة بالضبط

البيانةالمكانالجدول / البادئة
بلوكات التاجرD1block_configs
توكنات المتاجرD1stores
سجل أحداث الويبهوكD1webhook_events
صور التجّارR2product-blocks-media/{store_id}/
الجلساتلا تُخزَّن — موقّعة ويُتحقّق منها بالتوقيع
بيانات زوار المتاجرلا شيء إطلاقاً

أوامر جاهزة — الصقها في الطرفية من مجلد المشروع

مَن مثبِّتnpx wrangler d1 execute product-blocks-db --remote --command="SELECT store_id, installed_at, uninstalled_at FROM stores"
البلوكاتnpx wrangler d1 execute product-blocks-db --remote --command="SELECT store_id, product_id, status FROM block_configs"
آخر الأحداثnpx wrangler d1 execute product-blocks-db --remote --command="SELECT event, status, attempts FROM webhook_events ORDER BY rowid DESC LIMIT 20"
سجل حيّnpx wrangler tail product-blocks-api
مَن أناnpx wrangler whoami
⚠ ولو فتحت طرفية أمامهم

استعمل الاستعلامات أعلاه فقط. لا تشغّل SELECT * على stores — فيه التوكنات.


٣ · في المتجر التجريبي

هنا يحدث العرض الحيّ. افتح الاثنين قبل الاجتماع وأبقهما في تبويبين.

اللوحةلوحة متجرك التجريبي ← تطبيقاتي ← وصّاف ← «افتح التطبيق»
صفحة المنتجhttps://demostore.salla.sa/dev-cob1qywzf9tfg6ze/فستان/p417349872فتح
✓ الصفحة تعمل لكل زائر

بلا تسجيل دخول. جرّبها من جوالك أو من نافذة خفية قبل الاجتماع لتطمئن.

لو سألوكأرِهم
«كيف يبني التاجر؟»اللوحة ← إضافة بلوك ← عدّل نصاً وأشِر للمعاينة
«وين تظهر للزائر؟»صفحة المنتج — تحت الوصف مباشرة
«هل ترث ألوان القالب؟»قارن لون الأزرار في البلوكات بلون أزرار المتجر
«هل تعمل على الجوال؟»افتح نفس الرابط من جوالك — أقوى من أي شرح
«مسودة أم منشور؟»عدّل في اللوحة بلا نشر، وأعد تحميل صفحة المنتج — لا يتغير شيء

٤ · في الكود والوثائق

لو طلبوا مراجعة الكود أو ملفاً بعينه. كل شيء في G:\product-blocks.

لو سألوك عنالملف
المحرك — مصدرهdashboard/src/engine/index.js
المحرك — المبنيdashboard/public/engine/pb-engine.js
السنِبِت المسجَّل في سلةdashboard/snippet-loader.js
البلوكات الـ١٨dashboard/src/blocks/*.js
معالج الويبهوكsrc/webhook.js
التحقق من التوقيعsrc/lib/crypto.js
التوكنات وقفل التجديدsrc/lib/stores.js
نداءات واجهة سلةsrc/lib/salla.js
جلسة اللوحةsrc/lib/session.js
الصور والحصصsrc/lib/media.js
الموجّه والمهمة اليوميةsrc/index.js
الإعدادات والبيئةwrangler.toml
الصفحات القانونيةdashboard/public/legal/

والوثائق

لو احتجتالملف
هذا الملف نصّاًdocs/MEETING-BRIEF.md
حالة المشروع والقراراتdocs/PROJECT-CONTEXT.md
فخاخ سلة المثبتة بالتجربةdocs/SALLA-FACTS.md
خريطة دوكس سلةdocs/SALLA-DOCS-MAP.md
ما بقي قبل النشرdocs/BEFORE-PUBLISH.md
الهوية ونصوص المتجرdocs/BRAND-BRIEF.md

٥ · الروابط العامة

كلها تعمل بلا تسجيل دخول. وأي واحد منها يمكن أن ترسله لسلة مباشرة.

الخادمhttps://product-blocks-api.pomstore.workers.dev
الصحةhttps://product-blocks-api.pomstore.workers.dev/v1/healthفتح
الويبهوكhttps://product-blocks-api.pomstore.workers.dev/v1/webhook
اللوحةhttps://product-blocks-panel.pages.devفتح
المحركhttps://product-blocks-panel.pages.dev/engine/pb-engine.jsفتح
الخصوصيةhttps://product-blocks-panel.pages.dev/legal/privacyفتح
الشروطhttps://product-blocks-panel.pages.dev/legal/termsفتح
الأسئلةhttps://product-blocks-panel.pages.dev/legal/faqفتح
الدعمmohammed@naddaf.me

ونقاط النهاية عندنا

عامة  : /v1/health · /v1/blocks · /v1/webhook · /v1/theme · /v1/media/*
اللوحة: /v1/admin/{session, me, products, blocks, publish, settings, media}

أسئلة متوقعة وأجوبتها

اقرأ الجواب كما هو — كلها صحيحة ومبنية على الكود. افتح ما تحتاجه وقت السؤال.

كيف تضيفون البلوكات؟ هل تعدّلون وصف المنتج؟

لا، ولا حرفاً. البلوكات تُخزَّن عندنا كـ JSON، وسنِبِت التطبيق يرسمها في صفحة المنتج وقت التشغيل. إيقاف التطبيق يعيد كل شيء كما كان.

أين تضعونها في الصفحة؟ وكيف تحدّدون المكان؟

تحت وصف المنتج افتراضياً، والتاجر يختار: فوق الوصف أو تحته أو بدلاً منه.

ونحدّد المكان بأربعة محدّدات نجرّبها بالترتيب — أولها سمة سلة الرسمية data-testid="store-product-description". ولو لم نجد أياً منها لا نرسم شيئاً.

الشرح الكامل خطوة بخطوة ← تبويب «الربط التقني»

البلوكات مخزَّنة عندكم — فكيف تصل إلى صفحة المنتج؟

سلة تحقن سنِبِت التطبيق تلقائياً، والسنِبِت يجلب محرّكنا، والمحرك يقرأ معرّف المتجر والمنتج ثم يجلب البلوكات ويبني عنصراً جديداً مستقلاً يضعه جاراً لعنصر الوصف.

لا نكتب داخل أي عنصر يملكه القالب، ولا نستعمل innerHTML عليه. وكل ما نضيفه موسوم بـdata-pb فيُزال بدقة.

التفصيل الكامل ← تبويب «الربط التقني»

ماذا لو انتقل الزائر بين المنتجات بلا إعادة تحميل؟

هذه مشكلة واجهناها فعلاً: سلة تبدّل محتوى الصفحة بلا إعادة تحميل فيُمسح ما رسمناه، وonReady لا يُطلق ثانيةً. ولا حدث تنقّل موثَّق في Twilight.

فلا نعتمد على حدث أصلاً: نراقب الصفحة بـMutationObserver مؤجَّل، ونعيد الرسم حين يتغيّر المنتج فعلاً. ونراقب ولا نكتب على الجسم.

هل يعمل مع كل القوالب؟

لا يتطلب قالباً بعينه ولا تعديله. والبلوكات ترث --color-primary و --font-main من القالب تلقائياً. ولو لم نجد نقطة التثبيت، لا نرسم شيئاً — لا نكسر الصفحة.

كيف يعمل زر الإضافة للسلة؟

لا نستدعي واجهة السلة إطلاقاً. الزر عندنا يفوّض النقرة لزر سلة الأصلي في الصفحة، فيرث الكمية والخيارات والتحقق والتنبيهات كما هي. واجهة أجمل للزر نفسه.

من أين يأتي السعر في بلوك السعر؟

من صفحة المتجر نفسها. ننسخ عنصر السعر كما رسمته سلة، فيأتي بعملة الزائر وبنظام أرقام المتجر. ولو لم نجد سعراً لا نرسم البلوك إطلاقاً — لا نعرض رقماً افتراضياً.

أين تُخزَّن البيانات؟

Cloudflare — D1 للإعدادات و R2 للصور. ولا مزوّد آخر. والتفصيل الكامل في تبويب «وين ألقى كل شيء».

هل تجمعون بيانات عن زوار المتاجر؟

لا. لا كوكيز ولا تحليلات ولا تخزين. والاستثناء الوحيد: إن استعمل التاجر بلوك فيديو يوتيوب، فمشغّل يوتيوب يتبع سياسة جوجل — وهو مذكور صراحةً في سياسة خصوصيتنا.

ماذا يحدث عند إلغاء التثبيت؟

نمحو التوكنات فوراً، ونُبقي الإعدادات والصور ٣٠ يوماً ثم تُحذف نهائياً بمهمة مجدولة يومية.

والمهلة مقصودة: من يلغي بالخطأ ويعيد التثبيت خلال شهر يجد كل شيء كما تركه.

كم يبطئ صفحة المنتج؟

ملف واحد ~٤٥ ك.ب يُخدَّم مضغوطاً من شبكة توزيع عالمية. وخط الأيقونات لا يُحمَّل إلا إن استعمل التاجر بلوكاً فيه أيقونة.

ماذا لو تعطّل خادمكم؟

البلوكات لا تظهر مؤقتاً، والمتجر وصفحات المنتجات وسلة الشراء تعمل طبيعياً — لأننا طبقة إضافية لا جزء من المتجر.

كيف تمنعون تاجراً من رؤية بيانات تاجر آخر؟

العزل في بنية التخزين نفسها لا في شرط برمجي: كل استعلام يحمل store_id، وكل ملف تحت بادئة معرّف المتجر. وهوية المتجر تأتي من توكن سلة لا من مُدخل مستخدم.

هل يمكن للتاجر التجربة قبل أن يرى زبائنه؟

نعم — مسودة ونشر منفصلان. المسودة تُحفظ تلقائياً كل ثانيتين ولا يراها أحد، والنشر هو ما يجعلها تظهر.

ما حدود التخزين للتاجر؟

٥ جيجابايت و١٠٠٠ صورة لكل متجر، والصور تُضغط في متصفح التاجر قبل الرفع. ولا نستضيف فيديو — بلوك الفيديو يقبل رابطاً خارجياً فقط.

كيف يدفع التاجر؟ وهل ترون بيانات بطاقته؟

٤٩ ريالاً شهرياً تُحصَّل عبر سلة من محفظته أو وسيلة دفعه المرتبطة. ولا نستلم مدفوعات ولا نرى بيانات بطاقات إطلاقاً.


ثلاثة بنود سيُسأل عنها حتماً

لا تنتظر أن تُكتشف — اذكرها أنت أولاً. المخالفة المكشوفة يُدافَع عنها، والمخفيّة تُقرأ مسألة نزاهة. وفي كل واحدة جواب دعمكم أو تبريركم الخاص.

١) تضمين يوتيوب داخل الصفحة

قواعد السنِبِتس تمنع iframe، ونحن نستعمله في بلوك الفيديو.

◆ ما تقوله

نستعمل تضمين يوتيوب في بلوك الفيديو، ونعرضه عليكم صراحةً ونطلب استثناءً.

لماذا: لا بديل يعطي التجربة نفسها. البديل الوحيد صورة تفتح يوتيوب في تبويب جديد — تُخرج الزائر من صفحة المنتج وتقتل معدل التحويل.

وما فعلناه لتقليل الأثر:

  • نستعمل youtube-nocookie.com وهو الأقل تتبّعاً
  • loading="lazy" — لا يُحمَّل شيء قبل أن يضغط الزائر التشغيل
  • الفيديو لا يُستضاف عندنا إطلاقاً
  • ومذكور صراحةً في سياسة خصوصيتنا

ولو رأيتم أنه غير مقبول، البديل جاهز عندنا ونحذفه.

⚠ ولا تقل «حاولنا إخفاءه»

كان في الكود تمويه بسيط لاسم العنصر وحذفناه، والكود المرفوع اليوم نظيف وغير مموّه — يكتب createElement('iframe') صراحةً.

٢) المحرك مستضاف على نطاقنا

السنِبِت في البورتال محمِّل ١١٩٤ حرفاً يسحب المحرك من product-blocks-panel.pages.dev.

◆ ما تقوله

سألنا الدعم عن هذا قبل أن نبنيه، وجاء الجواب:

«لا مانع من استضافته على نطاقكم. وسنراجعه إما بالوصول إلى البرنامج النصي أو بطلب مشاركته من المطور.»

ولماذا فعلناه:

  • حد الـ٥٠ ألف حرف — كنا عند ٨٨٪ منه
  • كاش صفحة المنتج يُبقي كوداً قديماً عند الزوار مدة غير معلومة، ولا سبيل لكسره عن زائر حقيقي
  • وهكذا يصل أي إصلاح عاجل خلال دقائق لا انتظاراً للكاش

والملف متاح لكم في أي وقت — والكود المصدري كامل ومعلَّق بالعربية.

جاهز لو طلبوهhttps://product-blocks-panel.pages.dev/engine/pb-engine.jsفتح

٣) مراقبة document.body

إرشاداتكم تقول «لا تستخدم body كمرجع مباشر»، ونحن نراقبه بـMutationObserver — لأنه الآلية الوحيدة التي تُبقي البلوكات بعد التنقّل الداخلي، إذ لا حدث تنقّل موثَّق في Twilight.

◆ الخلاصة

سألنا الدعم صراحةً ولم يمنعه، والقاعدة واردة في سياق innerHTML. ونحن نراقب ولا نكتب على الجسم إطلاقاً.

والشرح الكامل — بجواب الدعم حرفياً وبالبديل الذي نطلبه منهم — في تبويب «الربط التقني» ← كيف تظهر البلوكات ← البند ٤.


سيناريو العرض الحيّ — ٤ دقائق

الألم أولاً، ثم الحل. ولو قاطعوك بسؤال، جاوب من الأسئلة أعلاه وعُد لموضعك.

  1. ابدأ بالمشكلة ٣٠ ث
    أرِهم وصف منتج نصياً عادياً بلا بلوكات.

    «هذا كل ما يراه زائر التاجر اليوم.»

  2. افتح اللوحة داخل سلة ٣٠ ث
    لوحة المتجر ← تطبيقاتي ← وصّاف ← افتح التطبيق. وأشِر إلى الأعمدة الثلاثة: البلوكات · المعاينة · التخصيص.

    «اللوحة مدمجة — التاجر لا يغادر سلة.»

  3. ابنِ بلوكاً أمامهم ٦٠ ث
    اختر منتجاً ← «إضافة بلوك»«الضمانات والمزايا». عدّل نصاً من عمود التخصيص، وأشِر إلى المعاينة وهي تتغيّر فوراً.

    «ما تراه هنا هو ما سيراه الزائر.»

  4. أضف «الأسئلة الشائعة» ٣٠ ث
    يُظهر التنوّع ويبني القيمة.
  5. انشر ١٥ ث

    «الحفظ تلقائي والمسودة لا يراها أحد. والنشر هو ما يظهرها.»

  6. افتح صفحة المنتج — وعلى الجوال ٤٥ ث
    هنا يكتمل الأثر. وأظهر أن البلوكات ورثت ألوان المتجر.
  7. أغلق بلقطة الثقة ٣٠ ث
    افتح /v1/healthok: true

    «الخادم حيّ، والصلاحيتان قراءة فقط، ولا نلمس وصف المنتج.»


كن صادقاً في هذه

لا تدّعِ ما لم يحدث. المراجع يكتشف، والصدق يبني ثقة تنفعك في كل جولة قادمة.

لو سألواقل
«على كم متجر جُرّب؟»«متجر تجريبي واحد بثيم رائد، ومتجري الشخصي. لم يستعمله تاجر آخر بعد»
«كم عدد المستخدمين؟»«صفر — هذا أول إصدار»
«هل جُرّب على كل الثيمات؟»«لا. جرّبناه على رائد، والمحدّدات مكتوبة لتعمل على غيره، لكننا لم نختبر كل الثيمات. ولو لم نجد نقطة التثبيت لا نرسم شيئاً — لا نكسر الصفحة»
⚠ ولا تقل إطلاقاً

«يزيد المبيعات ٣٠٪» أو أي نسبة. لا نملك بياناً يدعمها.

◆ وبند لم يُختبر حيّاً — اذكره إن سألوا عن الاشتراكات

أحداث الاشتراك مبنية ومعالَجة، لكنها لم تصل فعلياً بعد لأن التطبيق ما زال development بلا باقة منشورة. اختبرنا منطق البوابة يدوياً، والتحقق الحقيقي عند أول تاجر.

ملخّص في ثلاثة أسطر

وصّاف يضيف بلوكات إقناع تحت وصف المنتج بلا كود وبلا لمس القالب.

صلاحيتان قراءة فقط · Easy Mode · ويبهوك موقَّع بـ HMAC · وكل شيء على Cloudflare.

وبندان نعرضهما صراحةً: تضمين يوتيوب، والمحرك على نطاقنا بموافقة الدعم المكتوبة.