الانتقال إلى المحتوى
Micro Frontend

ضبط المستخدم والمصادقة

يعرض Micro-Frontend متجر التذاكر بصورة أصلية داخل صفحتكم. يحدد backend الخاص بكم هوية المستخدم، وتكون الصفحة المضيفة مسؤولة عن مصادقته. وجود session token إلزامي، إذ يبقى Micro-Frontend غير نشط من دونه. توضح هذه الصفحة السبب ومسار session بين الخوادم.

التكنولوجيا وراء الواجهة الصغيرة

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

React 19 — متوافق حتى React 18

يعمل التدفق الكامل — الأحداث، والمقالات، وعربة التسوق، والتسجيل، والدفع، والإكمال — كتدفق React واحد وآمن تمامًا من حيث النوع. نحن نعمل بالفعل على React 19 بأنفسنا، ولكننا نبقي التوافق مفتوحًا عمدًا وصولاً إلى React 18 (تفاعل PeerDependency >=18) بحيث يتم تضمين الواجهة الأمامية الصغيرة في أكبر عدد ممكن من الصفحات المضيفة. يُقصد بنطاق النظراء هذا حرفيًا: داخليًا، يستخدم المحول فقط مجموعة ميزات React 18 (useState، useEffect، useMemo & co.) ولا توجد واجهات برمجة تطبيقات حصرية لـ React-19 - تم إنشاؤها واختبارها ضمن React 19، ومضمونة التشغيل تحت React 18. React هي مجرد اعتمادية نظيرة: لا يجلب المحول أي تبعيات وقت تشغيل خاصة به. لا توجد مكتبة لواجهة المستخدم، ولا إطار عمل لجلب البيانات، ولا مجموعة أدوات الحالة - فقط خطافات React منتقاة بعناية. والنتيجة هي حزمة صغيرة لا تحتوي على أي شيء يسحب إلى صفحتك. (API)

Light DOM بدلاً من iframe

لا يوجد iframe ولا ظل DOM ولا يوجد جسم غريب. يتم عرض المتجر مباشرة في DOM الخاص بصفحتك: فهو يرث الطباعة الخاصة بك، ويتناسب مع تخطيطك، وهو سريع الاستجابة ويمكن الوصول إليه بالكامل - ويقرأ للزائر كجزء أصلي من موقع الويب الخاص بك.

إطار حيادي: حامل واحد () في كل مكان (mount())

استدعاء واحد - mount(container, config) - يربط المتجر بأي عنصر DOM. سواء كان React أو Vue أو Angular أو HTML عادي: يكون المحول محايدًا للإطار ويعمل بنفس الطريقة في كل مكان.

التصميم لك

يحمل كل عنصر خطاف CSS مستقرًا (فئات BEM aditus-shop__* بالإضافة إلى حالة البيانات). يمكنك التصميم بحرية عبر CSS الخاص بك - أو تعيين ألوان علامتك التجارية، ونصف القطر، والمسافات في وقت قصير جدًا باستخدام مجموعة من رموز التصميم --aditus-*. اختياريًا باستخدام الأنماط الأساسية المعزولة بشكل نظيف عبر طبقة CSS التي لا تتجاوز صفحتك أبدًا.

متعدد اللغات، لايف للتحويل

تم دمج DE/EN. يتم دفع مفتاح اللغة مباشرة إلى المثيل الجاري تشغيله عبر Handle.update() - دون إعادة تعيين رحلة المستخدم. يتم الحفاظ على العربة والتقدم.

آمن حسب التصميم

بدون رمز مميز صالح للجلسة، يكون المحول غير نشط — ومن ثم لا يقدم طلبًا واحدًا. يتم إنشاء الجلسة من خادم إلى خادم بسر؛ لا يصل أي مفتاح إلى المتصفح على الإطلاق. يتم تشغيل جميع المكالمات من خلال وكيل يقوم بإدخال بيانات الاعتماد من جانب الخادم.

متطلبات البيئة المضيفة

في طبقة التكامل، تكون الواجهة الأمامية الصغيرة متوافقة إلى أقصى حد عن عمد — أي إطار عمل، وأي CSS مضيف، وReact 18+. النقاط الثابتة التي لا يمكن تجنبها قليلة وغير ضارة: عميل في المتصفح، وReact موجود، وجلسة مع المفتاح العام المدرج في القائمة البيضاء. (publicKey)

ما هو مطلوب

  • React 18+ وreact-dom 18+ في الحزمة المضيفة - تم تعريفهما كتفاعل PeerDependency >=18؛ يستخدم mount() createRoot من React-dom/client. يقرر المضيف إصدار React.
  • متصفح مزود بـ DOM (جانب العميل). يتم تركيب المحول في عنصر DOM حقيقي (Light DOM). يعد مضيفو SSR جيدًا طالما أن mount() يعمل على العميل.
  • متصفح دائم الخضرة حاليًا - يستخدم واجهات برمجة تطبيقات الويب القياسية (fetch، AbortController، ResizeObserver، CSS@layer)، خط الأساس منذ عام 2022 تقريبًا. لا آي إي. (API)
  • جلسة بالإضافة إلى المفتاح العام - بدون رمز الجلسة، تظل الواجهة الأمامية الصغيرة غير نشطة. تحويل خادم إلى خادم باستخدام السر (مضيف ذو واجهة خلفية) أو من المتصفح عبر جلسة /shop/embed-بوابة المجال (مضيف بدون واجهة خلفية). يجب أن يكون المجال مدرجًا في القائمة البيضاء لـ publicKey. (sessionToken)

ما لا نطلبه عمدا

  • لا يوجد إطار عمل محدد - يعتبر mount(container, config) محايدًا لإطار العمل: React وVue وAngular وHTML العادي.
  • لا يوجد إطار iframe، ولا يوجد ظل DOM - يتم عرضه أصلاً في صفحتك.
  • لا يوجد مضيف CSS معين - الأنماط الأساسية موجودة في @layer aditus-shop (أي قاعدة مضيف غير طبقة تفوز)، في خصوصية فئة واحدة، بالإضافة إلى إعادة تعيين الدرع ضد المضيف * { هامش: 0؛ الحشو: 0 }. قابل للتحويل بالكامل باستخدام الأنماط الأساسية: false.
  • لا توجد تبعيات وقت التشغيل - فقط React PeerDeps، ولا شيء يتعارض في حزمة المضيف.
  • لا يوجد إعداد بناء محدد يتجاوز استيراد ESM.

أمان الدفع، PCI وCSP

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

لا توجد بيانات الدفع في ضوء DOM

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

الحد الأدنى من نطاق PCI

نظرًا لعدم معالجة أي بيانات خاصة ببطاقة الائتمان أو البنك أو نقلها أو تخزينها في تطبيقك المضيف، تظل بيئتك في الحد الأدنى من نطاق PCI-DSS لنموذج إعادة التوجيه الكلاسيكي (عادةً SAQ A - يتم دائمًا إجراء التصنيف الملزم بواسطة الجهة المستحوذة أو QSA). طرق الدفع بدون إعادة توجيه، مثل الفاتورة، كاملة من جانب الخادم - مع عدم وجود بيانات دفع في المتصفح على الإطلاق.

سياسة أمان المحتوى الهزيل

علاوة على سياستك الحالية، تحتاج الواجهة الأمامية الصغيرة نفسها إلى سماحين فقط: Connect-src لأصل ADITUS API (كل مكالمة متجر تمر عبرها) وimg-src لعناوين URL لأصول ADITUS إذا قمت بعرض شعار الحدث أو صور المقالة. على وجه الخصوص، لا يحتاج إلى إدخالات script-src أوframe-src لموفري الدفع، لأن خطوة الدفع عبارة عن إعادة توجيه عادية على المستوى الأعلى: لا يتم تحميل نصوص برمجية للدفع ولا إطارات دفع داخل صفحتك على الإطلاق.

أفضل ما في العالمين

تقليديًا، كان عليك الاختيار بين: إطار iframe (جسم غريب معزول ولكنه جامد ومرئي) أو بناء واجهة برمجة تطبيقات (API) متكامل تمامًا (سلس، لكن فريق الأمان الخاص بك أصبح فجأة يمتلك نطاق PCI الكامل). تعمل الواجهة الأمامية المصغرة على حل هذه المعضلة: التفاعل غير الضار — التصفح، وعربة التسوق، وتكوين التذاكر — يعيش أصلاً في صفحتك؛ أما التفاعل النقدي – جمع الأموال – فهو معزول بشدة ومعزول خارجيًا.

كيف يختلف عن بدائل السوق

مقابل iframe الكلاسيكي

تعتبر إطارات Iframe آمنة بفضل العزل الصارم للمتصفح، ولكنها تتصرف مثل المستندات المعزولة: فهي لا ترث أنماطًا عالمية مثل الطباعة أو قواعد التخطيط المستجيبة، ولا يمكن فهرسة محتواها بشكل موثوق لبرامج الزحف؛ بالنسبة لقارئي الشاشة، غالبًا ما يعني ذلك فاصلًا للوسائط. تعرض الواجهة الأمامية المصغرة إعداد الشراء بالكامل - اختيار المقالة، سلة التسوق، التسجيل - كـ HTML حقيقي مباشرة في DOM الخاص بصفحتك، مما يجعلها سهلة الوصول وسريعة الاستجابة مثل العلامات الخاصة بك. يتم إعادة إنشاء التأثير العازل لإطار iframe فقط في لحظة الدفع: حيث يتم تسليم تدفق البيانات المهمة بالكامل، عبر إعادة توجيه عالية المستوى، إلى صفحة الدفع الخارجية المخصصة.

المرونة مقابل Shadow DOM النقي

تقوم العديد من مكونات الويب بتغليف نفسها في ظل DOM لتجنب تعارض خصوصيات CSS - من الناحية العملية، يؤدي هذا غالبًا إلى علامة تجارية غير مكتملة، لأن الأنماط العامة محظورة ويجب تمرير قواعد التصميم بشكل شاق عبر سمات الجزء أو الخصائص المخصصة. تستخدم الواجهة الأمامية المصغرة سلسلة CSS الأصلية بدلاً من ذلك: تعيش أنماطها الأساسية في @layer aditus-shop، وهي طبقة منخفضة عمدًا - كل قاعدة CSS غير ذات طبقات في صفحتك تفوز تلقائيًا. يتم توريث تصميم شركتك محليًا، دون حروب محددة ضد مكونات واجهة المستخدم الخاصة بالمتجر.

كفاءة الموارد وحجم الحزمة

تقوم الأدوات المتجانسة في كثير من الأحيان بشحن عملاء HTTP وإدارة الحالة ومكتبات واجهة المستخدم الخاصة بهم - ويمكن أن تجبر المتصفح على تحميل أوقات التشغيل الزائدة بالتوازي، مما يضر بوقت تحميل صفحتك (Core Web Vitals). تعلن الواجهة الأمامية المصغرة أن React وReact-DOM حصريًا على أنهما PeerDependency وليس لديهما أي تبعيات وقت تشغيل خاصة بها: إذا كانت صفحتك تستخدم بالفعل React 18 أو إصدار أحدث، فسيتم ربطها مباشرة بوقت التشغيل الحالي. بالنسبة إلى البيئات غير التابعة لـ React، يقوم مكون الويب الاختياري بتغليف تبعية React في حزمتها المعزولة، دون شغل المتغيرات العامة على صفحتك.

مقارنة النهج

معيارإطار iframe الكلاسيكيأداة Shadow-DOMالواجهة الأمامية الصغيرة لـ ADITUS
تحسين محركات البحث وإمكانية الوصوليتم حفظ المحتوى في مستند منفصل - غالبًا ما يكون غير مرئي لبرامج الزحف، وهو أمر أكثر صعوبة بالنسبة لقارئات الشاشة.معزول؛ عادةً ما تحتاج قابلية الاكتشاف وإمكانية الوصول إلى عمل إضافي.الجزء الأصلي من DOM الخاص بك - قابل للفهرسة ويمكن الوصول إليه مثل العلامات الخاصة بك.
التصميم والعلامات التجاريةجامد: لا يوجد وراثة CSS، والتخصيص فقط عبر واجهات برمجة تطبيقات postMessage. (API)الأنماط العالمية لا تصل إلى الداخل؛ السمات فقط عبر الخصائص والأجزاء التي تم تمريرها.يفوز CSS الخاص بك بحكم التعريف عبرlayer؛ الجلد الأساسي قابل للتجاوز أو معطل بالكامل.
الحزمة والأداءيقوم عادةً بتحميل تطبيق ثانٍ كامل بما في ذلك وقت التشغيل الخاص به داخل الإطار.غالبًا ما يتم شحن وقت التشغيل الخاص به والتبعيات المكررة.يستخدم رد فعل صفحتك؛ صفر تبعيات وقت التشغيل من تلقاء نفسها. (React)
أمان الدفع ونطاق PCIمعزولة بشدة - ولكن على حساب تجربة المستخدم والتصميم والاستجابة.ستتدفق بيانات الدفع عبر DOM المضيف إذا تم التقاطها هناك.الاختيار في ضوء DOM، والدفع معزول بالكامل على صفحة الدفع الخارجية.

كيف تحصل على الواجهة الأمامية الصغيرة

الواجهة الأمامية الصغيرة ليست على npm العامة وليست على CDN. يتم تسليمها كحزمة مصدر مباشرة من مثيل ADITUS الخاص بك: TypeScript وESM، مكتوبة بالكامل، بدون أي تبعيات وقت تشغيل خاصة بها - يقوم المجمع الخاص بك (Vite، webpack، Next.js، Nuxt) بتجميعها مع تطبيقك مثل التعليمات البرمجية الخاصة بك. اسم الاستيراد الموجود في المقتطفات الموجودة في هذه الصفحة (@workspace/aditus-shop-embed) هو اسم الحزمة من التكامل المرجعي الخاص بنا؛ الحزمة القابلة للتنزيل تحمل اسم @aditus/shop-embed — واجهة برمجة التطبيقات متطابقة. (API)

ما يحتويه التسليم

المحول الكامل كمصدر TypeScript قابل للقراءة مع إعلانات النوع لكل خيار تكوين ورد اتصال وحدث. React وreact-dom يظلان تبعيات نظيرة — صفحتك توفرهما، لذلك لا شيء يتعارض في حزمتك ولا يوجد React آخر.

الحصول على الحزمة

الحزمة متطابقة لكل مضيف ويتم تنزيلها مباشرة من هذه الحالة، دون الحاجة إلى بيانات اعتماد:{{API_BASE_URL}}/api/embed/v1/aditus-shop-embed.tgz. قم بتثبيته مباشرة من عنوان URL هذا (يقبل تثبيت npm عنوان URL tarball — استخدم الرابط المستهدف في المثيل الخاص بك، المسار /api/embed/v1/aditus-shop-embed.tgz) أو قم بفك ضغطه في الريبو الخاص بك. ما يوفره الإعداد فعليًا هو المفتاح العام الخاص بك مع القائمة البيضاء للنطاق الخاص به؛ وبدونها تظل الواجهة الأمامية الصغيرة غير نشطة. جهة الاتصال: صفحة الاتصال. لا يحتاج المضيفون الذين ليس لديهم خطوة البناء الخاصة بهم (WordPress، HTML العادي) إلى الحزمة المصدر على الإطلاق: فهم يستخدمون حزمة Web Component ذاتية الاستضافة الموضحة في القسم التالي. (publicKey)

مكون الويب: لا توجد خطوة بناء مطلوبة (Web Component)

بالنسبة للصفحات التي لا تحتوي على أداة تجميع خاصة بها - WordPress، وTypo3، وHTML العادي - يتم أيضًا شحن الواجهة الأمامية المصغرة كحزمة مُنشأة مسبقًا ومستضافة ذاتيًا: تسجل علامة نص برمجي واحدة عنصر <aditus-shop>، مع تضمين React، بدون npm، بدون إنشاء. يتم تسليمه من مثيل ADITUS الخاص بك ضمن عنوان URL ذي الإصدار (/api/embed/v1/…)، وليس من شبكة CDN عامة - لهذا النظام التجريبي الذي هو https://developers.aditus.com/api/embed/v1/aditus-shop.js. عنوان URL عام عمدًا ولا يتطلب أي بيانات اعتماد: الحزمة عبارة عن كود منشور عادي، والبوابة الفعلية هي مفتاحك القابل للنشر مع القائمة البيضاء للنطاق الخاص بها بالإضافة إلى الجلسة. هذا المسار اختياري تمامًا وإضافي تمامًا: يستمر المضيفون الذين لديهم بنيتهم ​​الخاصة في دمج الحزمة المصدر عبر mount() تمامًا كما هو موثق في هذه الصفحة - نفس الرحلة، ونفس خطافات CSS، ونفس السمات.

index.html
<!-- 1. Das selbst gehostete Bundle einbinden (einmal pro Seite). -->
<script src="https://ihre-aditus-instanz.de/api/embed/v1/aditus-shop.js" defer></script>

<!-- 2. Das Element platzieren — der Shop rendert nativ an dieser Stelle. -->
<aditus-shop
  public-key="pk_live_ihrkey"
  culture="de-DE"
></aditus-shop>

<!-- 3. Optional: Journey-Events als ganz normale DOM-Events konsumieren. -->
<script>
  document.querySelector("aditus-shop").addEventListener("cart:update", (e) => {
    console.log("Positionen im Warenkorb:", e.detail.itemCount);
  });
</script>
يصفمعنى
public-keyمفتاحك القابل للنشر (تنطبق القائمة البيضاء للنطاق). باستخدام مجموعة السمات هذه فقط، يقوم العنصر بإنشاء جلسة متصفح خاصة به - وهو الوضع الصحيح للصفحات التي لا تحتوي على واجهة خلفية.
session-tokenجلسة تم سكها بواسطة الواجهة الخلفية لديك (من خادم إلى خادم، انظر أدناه). إذا تم تعيينه، فلن يقوم العنصر بتكوين نفسه أبدًا — تمتلك صفحتك دورة حياة الجلسة.
api-baseعنوان URL الأساسي لواجهة برمجة تطبيقات ADITUS. الإعدادات الافتراضية للأصل الذي تم تحميل البرنامج النصي للحزمة منه - عادةً لا تقوم بتعيين هذا مطلقًا. (API)
cultureلغة/لغة المتجر، على سبيل المثال. de-DE أو en-GB. يؤدي تغيير السمة إلى إعادة التوطين في مكانه — ويتم الحفاظ على رحلة المستخدم.
article-layout / event-layout / article-select-mode / show-headerخيارات العرض المسطحة، نفس قيم تكوين mount() (البطاقات، الكمية، ...). نقطة الإدخال (منتقي الأحداث مقابل الحدث المثبت) ليست سمة: فهي ثابتة من جانب الخادم عند سك الجلسة.
survey-columnsأعمدة خيارات الإجابة (1–4) لأسئلة استطلاع RadioButtonList/CheckBoxList — مثل أعمدة الاستطلاع في تكوين mount(). يؤدي عدم التعيين إلى الاحتفاظ بالعمود الفردي الافتراضي (أو جانب الخادم الافتراضي المخزن للعميل)؛ تنهار إطارات العرض الضيقة مرة أخرى إلى عمود واحد.
base-stylesاضبط على "خطأ" لإسقاط المظهر الافتراضي المحايد وتصميم كل خطاف بنفسك.

الجلسة والشفاء الذاتي

تنطبق قواعد الجلسة لهذه الصفحة دون تغيير. باستخدام الواجهة الخلفية، يمكنك إنشاء خادم إلى خادم وتعيين رمز مميز للجلسة - ولن يتم إنشاء العنصر من تلقاء نفسه أبدًا. بدون واجهة خلفية، قم بتعيين المفتاح العام فقط: يقوم العنصر بإصدار جلسة متصفح عبر نقطة النهاية المرتبطة بالنطاق وإعادة الإصدار بشفافية عند انتهاء صلاحيتها، بحيث لا يصل المستخدم أبدًا إلى طريق مسدود. Light DOM كما هو الحال دائمًا: لا يوجد إطار iframe، ولا يوجد DOM الظل - يصل CSS الخاص بك إلى كل رابط موثق.

الأحداث والإصدارات

يظهر ناقل الأحداث كأحداث DOM CustomEvents عادية على العنصر (فقاعة، الحمولة في events.detail) - عربة التسوق: التحديث، الدفع: اكتمال ومرآة التحليلات، نفس الأسماء والحمولات مثل Handle.on(). يحمل عنوان URL الإصدار الرئيسي: يتلقى الإصدار v1 تحديثات متوافقة في مكانها؛ يتم شحن التغيير العاجل كـ /embed/v2/ لذلك لن يتغير أي شيء ضمن صفحتك دون سابق إنذار. كل شيء يتجاوز السمات المسطحة - كائنات السمات، وعمليات الاسترجاعات مثل onUserRequired، ومحتوى البطاقة - يظل ميزة حزمة المصدر حسب التصميم.

صفات وليست خصائص

يتم تكوين العنصر حصريًا من خلال سمات HTML — وتكون السمات دائمًا عبارة عن سلاسل. الأنماط الأساسية = "خطأ" هي السلسلة الحرفية "خطأ"؛ يقوم العنصر بتوزيعه، ولا يمكنك أبدًا تعيين خصائص JavaScript. تربط الأطر الأسماء الموصولة مثل المفتاح العام كسمات على أي حال، لذلك ليست هناك حاجة إلى بناء جملة ربط خاص. أي شيء لا يمكن التعبير عنه كسلسلة مسطحة - كائنات السمات، ردود الاتصال، محتوى البطاقة - لا يعد سمة عمدًا: لذلك، استخدم الحزمة المصدر.

إعادة ضبط الرحلة عمدا

الاستثناء الوحيد لـ "السمات فقط": يكشف العنصر عن طريقة ClearJourney() - النظير الأقل بناءًا لتصدير ClearJourney() للحزمة المصدر. اتصل بـ document.querySelector("aditus-shop").clearJourney() (على سبيل المثال من زر "البدء من جديد") قبل إعادة إضافة العنصر أو إعادة تحميله، ويتم إسقاط سجل الرحلة المستمر محليًا للصفحة الحالية - يبدأ التثبيت التالي من جديد عند نقطة الإدخال بدلاً من الاستئناف. تتم إزالة مؤشر السيرة الذاتية المحلي فقط؛ لم يتم تغيير أي حالة عربة تسوق من جانب الخادم.

التحديثات دون إعادة تحميل

يؤدي تغيير إحدى السمات في العنصر المباشر إلى تحديث المثيل قيد التشغيل في مكانه - تمامًا مثل Handle.update() في الحزمة المصدر. تحافظ سمات العرض التقديمي (الثقافة والتخطيطات) على رحلة المستخدم؛ يؤدي تغيير هوية الجلسة فقط (المفتاح العام، رمز الجلسة، قاعدة واجهة برمجة التطبيقات) إلى إعادة تأسيس الجلسة. تؤدي إزالة العنصر من DOM إلى إلغاء تحميل المتجر بشكل نظيف، بما في ذلك جميع المستمعين.

مضيفو SSR: Next.js وNuxt

الواجهة الأمامية الصغيرة مخصصة للعميل فقط. محتواه مباشر ومقيد بالجلسة - التوفر والأسعار وسلة تسوق المستخدم موجودة فقط لجلسة مصادق عليها في وقت الطلب، لذلك لا يوجد شيء ذو معنى يمكن للخادم المضيف عرضه مسبقًا. ينشئ mount() جذر React جديد من جانب العميل (createRoot)؛ لا يتم ترطيب العلامات المقدمة من الخادم داخل الحاوية. وهذا يجعل تكامل SSR بسيطًا ويمكن التنبؤ به: قم بعرض عنصر نائب على الخادم، وتثبيته على العميل، وحجز المساحة حتى لا يقفز أي شيء.

ticketshop-section.tsx
// Next.js (App Router) — das Micro-Frontend ist bewusst client-only.
"use client";
import { useEffect, useRef } from "react";
import { mount, type ShopHandle } from "@workspace/aditus-shop-embed";

export function TicketshopSection({ sessionToken }: { sessionToken: string }) {
  const el = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (!el.current) return;
    const handle: ShopHandle = mount(el.current, {
      publicKey: "pk_deine_seite",
      sessionToken, // server-seitig gemintet, als Prop an die Client-Komponente
    });
    return () => handle(); // Unmount beim Routenwechsel
  }, [sessionToken]);

  // Server und Client rendern DENSELBEN Platzhalter — kein Hydration-Mismatch.
  // min-height reserviert den Platz, damit die Seite beim Mount nicht springt (CLS).
  return <div id="aditus-shop" ref={el} style={{ minHeight: 560 }} />;
}

تجنب تغيير التخطيط (CLS)

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

الجلسة في مضيف SSR

يتمتع مضيفو SSR بميزة طبيعية: يمكن لوقت تشغيل الخادم الذي يعرض الصفحة أيضًا سك الجلسة - من خادم إلى خادم مع السر، تمامًا مثل تدفق النعناع أدناه (Next.js: Route Handler أو Server Component؛ Nuxt: مسار الخادم). قم بتسليم الرمز غير الشفاف الناتج إلى مكون العميل كدعم أو حمولة. لا يظهر السر مطلقًا في رمز العميل، ويظل الرمز المميز في الذاكرة.

تفاصيل Next.js

قم بتمييز مكون التثبيت "استخدام العميل" وتثبيته في useEffect (انظر المقتطف). باستخدام جهاز توجيه الصفحات، يحقق التالي/الديناميكي مع ssr: false نفس الشيء. قم بإرجاع المقبض من التأثير كتنظيف بحيث يتم إلغاء تحميل المسار من جانب العميل بشكل نظيف. يمر مفتاح الثقافة الموجود على المثيل قيد التشغيل عبر Handle.update() دون إعادة تعيين الرحلة؛ تتم إعادة تحميل رمز جلسة جديد (كما في المقتطف) - وتنجو عربة التسوق من ذلك أيضًا، لأن الواجهة الأمامية الصغيرة تستعيد الرحلة على الحامل.

تفاصيل Nuxt / Vue

قم بلف هدف التثبيت في <ClientOnly> أو قم بتثبيته في onMounted — يحتاج المحول إلى عنصر DOM حقيقي في المتصفح. تذكر متطلبات حزمة المضيف: يجب تثبيت React و React-dom 18+ كتبعيات لتطبيق Nuxt الخاص بك؛ يتم عرض المتجر في حاويته الخاصة ولا يتداخل مع DOM الافتراضي لـ Vue.

تحتاج الواجهة الصغيرة إلى جلسة

لا رمز

لا يوجد رمز مميز للجلسة — تظل الواجهة الأمامية الصغيرة غير نشطة

بدون رمز مميز للجلسة، تعرض الواجهة الأمامية الصغيرة إشعارًا محايدًا ولا تبدأ أي استدعاءات لواجهة برمجة التطبيقات (API) على الإطلاق. لا يمكن ملء عربة التسوق، ولا تسجيل، ولا الخروج. تكون الجلسة إلزامية قبل تنشيط المتجر - ولكن ليس من الضروري أن تكون مرتبطة بمستخدم معروف: جلسة الضيف كافية لتصفح عربة التسوق وملؤها، ويمكن تحديد المستخدم الحقيقي لاحقًا عند الخروج (انظر تدفق المستخدم الاختياري أدناه).

mount.ts
import { mount } from "@workspace/aditus-shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount-Ziel #aditus-shop nicht gefunden");

// Ohne sessionToken bleibt das Micro-Frontend inaktiv: es zeigt nur einen
// neutralen Hinweis und startet KEINE API-Calls — kein Warenkorb,
// keine Registrierung, kein Checkout. Erst eine Session aktiviert den Shop.
mount(el, {
  publicKey: "pk_deine_seite",
  // sessionToken fehlt -> Shop wird nicht aktiviert.
});
لكل مستخدم مضيف

رمز الجلسة - مستخدم محدد

تقوم الواجهة الخلفية الخاصة بك بإصدار رمز مميز قصير العمر مرتبط بمستخدم واحد. تعمل الواجهة الأمامية المصغرة بعد ذلك كهذا المستخدم: عربة التسوق الخاصة به وبياناته. الرمز غير شفاف، ومنتهي الصلاحية، ومستخدم واحد ومرتبط بمفتاحك العام - ولا يصل سرك إلى المتصفح أبدًا. (publicKey)

mount.ts
import {
  mount,
  type ShopHandle,
  type AditusShopConfig,
} from "@workspace/aditus-shop-embed";

// getElementById kann null sein — in TypeScript sauber prüfen statt "!".
const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount-Ziel #aditus-shop nicht gefunden");

// AditusShopConfig typisiert alle Optionen — Autovervollständigung inklusive.
const config: AditusShopConfig = {
  publicKey: "pk_deine_seite", // Pflicht, sobald eine Session im Spiel ist
  sessionToken,                // vom Backend gemintet, nur im Speicher halten
};

const handle: ShopHandle = mount(el, config);

// Das Micro-Frontend sendet bei jedem Shop-Call den Header X-Aditus-Session.
// Beim Logout die Session serverseitig widerrufen und neu mounten/aktualisieren.

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

هل تريد بداية جديدة متعمدة بدلاً من ذلك - زر حقيقي "للبدء من جديد"؟ تقوم الحزمة بتصدير ClearJourney(): اتصل بها قبل التثبيت (أو إعادة التثبيت) وسيتم إسقاط سجل الرحلة المستمر محليًا للصفحة الحالية، لذلك يبدأ التثبيت التالي من جديد عند نقطة الإدخال. تتم إزالة مؤشر السيرة الذاتية المحلي فقط؛ لم يتم تغيير أي حالة عربة تسوق من جانب الخادم.

اختياري: تصفح بشكل مجهول، قم بتسجيل الدخول عند الخروج

ليس عليك أن تعرف من هو المستخدم مقدمًا. يمكنك التثبيت مع جلسة ضيف وتتيح الواجهة الأمامية الصغيرة للزائر المجهول تصفح الأحداث واختيار المقالات وملء سلة التسوق. فقط عندما يتجاوزون عربة التسوق - باتجاه التسجيل والخروج - سيطلب منك تحديد المستخدم الحقيقي، عبر رد الاتصال onUserRequired. ثم يستمر باقي التدفق مع الحفاظ على العربة.

  1. 1

    جبل مع جلسة ضيف

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

  2. 2

    يؤدي ترك العربة إلى تشغيل onUserRequired

    في اللحظة التي يتقدم فيها الزائر عبر عربة التسوق، تستدعي الواجهة الأمامية الصغيرة رد الاتصال الخاص بك مرة واحدة وتظهر حالة معلقة محايدة. قم بتوثيق المستخدم (تسجيل الدخول/SSO)، ثم قم بإنشاء جلسة جديدة مرتبطة بالمستخدم بنفس الطريقة تمامًا مثل الجلسة الأولى: خادم إلى خادم مع سرك. ويتم إنشاء المستخدم اللاحق من خلال نظام التحويل من جهاز إلى جهاز أيضًا - لا يصل السر أبدًا إلى المتصفح ولا يتم تعيين المستخدم مطلقًا في رمز العميل. يقوم رد الاتصال الخاص بك فقط بترحيل الرمز غير الشفاف الناتج. والأهم من ذلك، أن الواجهة الخلفية لديك تستمد تلك الهوية من جلسة المصادقة الخاصة بها (ملف تعريف الارتباط/JWT)؛ يجب ألا يقبل أبدًا البريد الإلكتروني أو معرف المستخدم من المتصفح، وإلا يمكن لزائر ضار أن يطلب جلسة شخص آخر ويقرأ بياناته الشخصية عبر الملء المسبق.

  3. 3

    قم بإرجاع الرمز المميز الجديد - يتم ترحيل العربة

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

  4. 4

    احذف onUserRequired لمستخدم ثابت

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

page.ts
import {
  mount,
  type ShopHandle,
  type AditusShopConfig,
} from "@workspace/aditus-shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount-Ziel #aditus-shop nicht gefunden");

// Ein GAST-Session-Token reicht zum Mounten: server-zu-server gemintet wie
// gehabt, nur OHNE das Feld email (keine Platzhalter-Adresse nötig). Der
// Besucher darf browsen UND den Warenkorb füllen, ohne dass ein konkreter
// Nutzer feststeht.
const config: AditusShopConfig = {
  publicKey: "pk_deine_seite",
  sessionToken: guestSessionToken,

  // Wird EINMAL aufgerufen, sobald der Besucher den Warenkorb Richtung
  // Registrierung verlässt. Hier authentifizierst du den Nutzer (Login/SSO)
  // und mintest server-zu-server eine NEUE, nutzergebundene Session.
  // Gib den frischen Token zurück -> das Micro-Frontend übernimmt ihn und baut
  // den Warenkorb unter dem identifizierten Nutzer neu auf (Artikel bleiben
  // erhalten). Gib null zurück, um abzubrechen -> der Besucher bleibt im
  // Warenkorb, ohne Fehler.
  onUserRequired: async () => {
    // Ruft NUR dein Backend auf – ohne Identität im Payload. Dein Backend liest
    // den eingeloggten Nutzer aus SEINER eigenen Session (Cookie/JWT) und mintet
    // server-seitig für GENAU diese Identität. Niemals die E-Mail aus dem Browser
    // übergeben – sonst könnte ein Angreifer hier eine fremde Adresse einsetzen
    // und über den Prefill an deren personenbezogene Daten gelangen.
    const res = await fetch("/api/shop/my-session", { method: "POST" });
    if (res.status === 401) return null;        // nicht eingeloggt -> im Warenkorb bleiben
    const { sessionToken } = (await res.json()) as { sessionToken: string };
    return sessionToken;
  },
};

const handle: ShopHandle = mount(el, config);

تقوم الصفحة المضيفة بمصادقة المستخدم

لا يقوم ADITUS بمصادقة المستخدمين النهائيين لديك. موقعك هو المصدر الوحيد لحقيقة هوية المستخدم - حيث تقوم بتشغيل تسجيل الدخول أو تسجيل الدخول الموحد (SSO) أو الجلسة الخاصة بك. لا يثق ADITUS إلا في رمز الجلسة الذي تعده الواجهة الخلفية لديك بمفتاح سري. سلسلة الثقة هي: مصادقتك تثبت هويتك، ورمزك المميز الذي تم سكه سرًا يضمن ذلك، والواجهة الأمامية الصغيرة تعرضه.

  1. 1

    موقعك يصادق المستخدم

    تسجيل الدخول، الدخول الموحّد (SSO)، جلسة الأعضاء - جلسة خاصة بك تمامًا. ADITUS غير مشارك هنا.

  2. 2

    الواجهة الخلفية الخاصة بك تنهي جلسة

    خادم إلى خادم مع المفتاح السري. تقوم بتمرير من يجب أن تكون الواجهة الأمامية الصغيرة بمثابة (البريد الإلكتروني) وتربطه بمفتاحك العام. (publicKey)

  3. 3

    تقوم الصفحة بتثبيت الواجهة الأمامية الصغيرة

    يتم تسليم الرمز غير الشفاف إلى المتصفح وتمريره كـ sessionToken إلى mount(). يبقى السر على الخادم الخاص بك.

  4. 4

    كل مكالمة تحمل الجلسة

    ترسل الواجهة الأمامية الصغيرة جلسة X-Aditus عند كل طلب؛ يقوم الوكيل بتحليل مستخدم سلة التسوق من الجلسة.

  5. 5

    لا يوجد رمز مميز → تظل الواجهة الأمامية الصغيرة غير نشطة

    بدون جلسة، لا يتم تنشيط الواجهة الأمامية الصغيرة مطلقًا: فهي تعرض إشعارًا محايدًا ولا تجري أي مكالمات - لا توجد عربة تسوق ولا دفع. اصنع جلسة لتشغيله.

1. النعناع على الواجهة الخلفية الخاصة بك

server.ts
// SERVER-SEITE deines Hosts — der Secret-Key verlässt NIE den Browser.
// Dein Backend hat den Nutzer bereits selbst authentifiziert (Login/SSO/Session).
// WICHTIG: user stammt aus DIESER Server-Session, nie aus dem Request-Body des
// Browsers — sonst könnte ein Angreifer eine fremde E-Mail unterschieben.
const res = await fetch("https://<dein-host>/api/shop/session", {
  method: "POST",
  headers: {
    // Mint-Secret DEINES Clients (im ADITUS-Admin erzeugt), nur server-seitig.
    Authorization: `Bearer ${process.env.ADITUS_MINT_SECRET}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    publicKey: "pk_deine_seite",      // bindet die Session an deinen Key (Pflicht)
    email: user.email,                 // wer das Micro-Frontend sein soll (weglassen = anonyme Gast-Session)
    externalUserId: user.id,           // optionale Metadaten (deine User-ID)
    ttlSeconds: 1200,                  // optional: Lebensdauer (Default 20 min, max 24 h)
    eventSlug: "ExperienceDaysv52024", // optional: Einstieg direkt in dieses Event (sonst Eventübersicht)
  }),
});

// Antwort-Typ deiner Wahl — sessionToken an den Browser reichen (inline oder fetch).
const { sessionToken }: { sessionToken: string; expiresAt: number } = await res.json();

2. جبل مع الرمز المميز

page.ts
import {
  mount,
  type ShopHandle,
  type AditusShopConfig,
} from "@workspace/aditus-shop-embed";

// getElementById kann null sein — in TypeScript sauber prüfen statt "!".
const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount-Ziel #aditus-shop nicht gefunden");

// AditusShopConfig typisiert alle Optionen — Autovervollständigung inklusive.
const config: AditusShopConfig = {
  publicKey: "pk_deine_seite", // Pflicht, sobald eine Session im Spiel ist
  sessionToken,                // vom Backend gemintet, nur im Speicher halten
};

const handle: ShopHandle = mount(el, config);

// Das Micro-Frontend sendet bei jedem Shop-Call den Header X-Aditus-Session.
// Beim Logout die Session serverseitig widerrufen und neu mounten/aktualisieren.

3. الإلغاء عند تسجيل الخروج

server.ts
// SERVER-SEITE — z. B. beim Logout deines Nutzers.
await fetch(`https://<dein-host>/api/shop/session/${sessionToken}`, {
  method: "DELETE",
  headers: { Authorization: `Bearer ${process.env.ADITUS_MINT_SECRET}` },
});
// { revoked: true }

يعود الطلب النهائي إلى صفحتك

عند اكتمال شراء التذكرة، لن تحتاج إلى بحث منفصل عن الطلب. نظرًا لأن الواجهة الأمامية المصغرة تعمل أصلاً في صفحتك (Light DOM، وليس iframe)، فإنها تقوم بتسليم الطلب النهائي مباشرة إلى التعليمات البرمجية الخاصة بك عبر رد الاتصال onComplete - وهو استدعاء JS مباشر، بدون رسالة لاحقة. لقد قامت الواجهة الأمامية المصغرة بحل الطلب لك بالفعل، لذلك تتلقى رقم الطلب وروابط التذكرة، وليس مجرد معرف.

mount.ts
import {
  mount,
  type ShopHandle,
  type CompletedOrder,
} from "@workspace/aditus-shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount-Ziel #aditus-shop nicht gefunden");

const handle: ShopHandle = mount(el, {
  publicKey: "pk_deine_seite",
  sessionToken,
  // Wird GENAU EINMAL aufgerufen, sobald die Bestellung platziert ist.
  // Das Micro-Frontend hat die Bestellung bereits für dich aufgelöst — du bekommst
  // die aufgelöste Bestellung, nicht nur eine ID.
  onComplete: (order: CompletedOrder) => {
    order.number;   // Bestellnummer (Order-ID als Fallback) — dein Identifier
    order.status;   // Status der Bestellung
    order.buyer;    // Käufer (Name / E-Mail), falls vorhanden
    order.tickets;  // [{ name, links: [{ kind: "pdf" | "apple" | "google", url }] }]

    // -> Order-ID speichern, Bestätigung zeigen, DOI / Newsletter anstoßen ...
  },
  // Wird bei jedem nicht behebbaren Fehler im Ablauf aufgerufen.
  onError: (err: Error) => {
    console.error("Shop-Fehler:", err.message);
  },
});

ما تتلقى

يتم تشغيل onComplete مرة واحدة لكل عملية دفع ناجحة مع الطلب الذي تم حله: الرقم (معرف الطلب / رقم الطلب - المعرف الخاص بك)، والحالة، والمشتري (الاسم والبريد الإلكتروني في حالة وجوده) والتذاكر - تحمل كل تذكرة روابط مكتوبة من النوع "pdf" أو "apple" أو "google". يتم تشغيل onError عند حدوث أي خطأ غير قابل للاسترداد في التدفق.

إعادة توجيه طرق الدفع

يتم تنشيط الطرق التي تظل على الصفحة (الفاتورة، الدفع المسبق، البطاقة المضمنة) عند الاكتمال على الفور. يتم التعامل مع الطريقة التي ترسل المتصفح إلى صفحة دفع خارجية (مثل Saferpay) من خلال Micro-Frontend من النهاية إلى النهاية: فهي تستمد عناوين URL للإرجاع من الصفحة التي تعمل عليها، وترسل المستخدم للخارج، وفي طريق العودة تنهي الطلب وتطلقه على اكتمل - رحلة ذهابًا وإيابًا كاملة بدون رمز على جانبك. فقط إذا كان الإرجاع يجب أن يصل إلى صفحة مختلفة، يمكنك تجاوز redirectUrls.successUrl / CancelUrl / errorUrl.

"إحصاءات Google‏ 4" / أحداث التجارة الإلكترونية (اختياري) (GA4)

يمكن للواجهة الأمامية الصغيرة الإبلاغ عن مسار التسوق إلى التحليلات الخاصة بك - ولكن فقط إذا طلبت ذلك. فهو لا يجمع أي gtag أو GTM أو GA SDK، ولا يقوم بتحميل أي شيء، ولا يقوم بتعيين أي ملفات تعريف ارتباط، ولا يرسل أي شيء من تلقاء نفسه. يمكنك إضافة رد اتصال اختياري واحد، onEvent، وتلقي الأحداث المكتوبة التي تم تشكيلها بالفعل في مخطط التجارة الإلكترونية المحسّنة على GA4 من Google. احذف oneEvent ولن يتغير شيء. إن الأداة التي تقوم بتغذيتها، وما إذا كانت لديك موافقة على إرسالها، تظل في صفك تمامًا.

mount.ts
import {
  mount,
  type ShopHandle,
  type ShopAnalyticsEvent,
} from "@workspace/aditus-shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount-Ziel #aditus-shop nicht gefunden");

const handle: ShopHandle = mount(el, {
  publicKey: "pk_deine_seite",
  sessionToken,
  // OPTIONAL. Lässt du onEvent weg, ändert sich nichts: das Micro-Frontend
  // sendet nichts, lädt kein gtag/GTM/GA-SDK und setzt keine Cookies.
  // Tool, Consent und Mapping bleiben komplett bei dir — hier GA4 via gtag.
  onEvent: (event: ShopAnalyticsEvent) => {
    // Consent liegt bei dir: erst senden, wenn der Nutzer zugestimmt hat.
    if (!hasAnalyticsConsent()) return;
    // Die Events sind bereits im GA4-Schema (name + items/value/currency ...),
    // also 1:1 an gtag durchreichen.
    const { name, ...params } = event;
    window.gtag?.("event", name, params);
  },
});

بالفعل في شكل GA4

يحمل كل حدث اسم حدث "إحصاءات Google" 4 بالإضافة إلى معلماته القياسية: العناصر (مع item_id، وitem_name، والسعر، والكمية، وitem_category، و- حيثما ينطبق - الخصم والقسيمة)، والقيمة والعملة؛ يضيف add_Payment_info نوع الدفع ويضيف الشراء معرف_المعاملة. قم بتدمير الاسم وتمرير الباقي مباشرة إلى gtag("event"، name، params) - لا حاجة إلى إعادة التعيين. (GA4)

الموافقة تبقى لك

onEvent هي مجرد وظيفة JavaScript عادية في صفحتك والتي نطلق عليها اسمًا مباشرًا. لا ترسل Micro-Frontend نفسها أي تحليلات في أي مكان - لا شيء يغادر الصفحة حتى يرسلها المعالج الخاص بك، لذلك تظل إدارة موافقتك تحت السيطرة الكاملة: بوابة إعادة التوجيه عند الموافقة، أو التوجيه إلى dataLayer في GTM بدلاً من أحداث gtag أو الدفعة أو إسقاط الأحداث بالكامل. يتم اكتشاف أي خطأ يرتكبه المعالج الخاص بك وعزله، لذلك لا يقطع تدفق المستخدم أبدًا - احتفظ بضوء المعالج (دفع gtag/dataLayer) نظرًا لأنه يعمل بشكل مضمّن.

حدثحرائق عندما
view_item_listيرى المستخدم قائمة المقالات (يتم تشغيلها مرة واحدة لكل مجموعة معروضة).
add_to_cartتتم إضافة مقال أو إضافة إلى سلة التسوق.
remove_from_cartتتم إزالة خط سلة التسوق أو الوظيفة الإضافية.
view_cartيتم عرض عرض سلة التسوق (مرة واحدة لكل عربة تسوق).
begin_checkoutيترك المستخدم العربة نحو التسجيل / الخروج.
add_payment_infoيتم اختيار طريقة الدفع (تحمل نوع_الدفع).
purchaseتم تقديم الطلب (يحمل معرف المعاملة والقيمة والعناصر).

ناقل الحدث: Handle.on / Handle.off (اختياري)

إلى جانب عمليات الاسترجاعات الخاصة بالتكوين، يحمل المقبض الذي يتم إرجاعه بواسطة mount() ناقل أحداث صغيرًا اختياريًا بالكامل. اشترك باستخدام Handle.on(event,lister) - يقوم بإرجاع وظيفة إلغاء الاشتراك المطابقة - ويفصل باستخدام Handle.off(event,lister). إذا لم تتصل مطلقًا بـ()، فلن يتغير شيء: لا تضيف الحافلة أي تبعيات، ولا ترسل شيئًا ولا تكلف شيئًا. إنها ملاحظة خالصة لصفحتك - شارة عربة صغيرة، ولافتة تأكيد، وتسجيل خاص بك - وهي تكمل عمليات الاسترجاعات بدلاً من استبدالها.

mount.ts
import {
  mount,
  type ShopHandle,
  type ShopCartUpdate,
} from "@workspace/aditus-shop-embed";

const el = document.getElementById("aditus-shop");
if (!el) throw new Error("Mount-Ziel #aditus-shop nicht gefunden");

const handle: ShopHandle = mount(el, {
  publicKey: "pk_deine_seite",
  sessionToken,
});

// OPTIONAL. Ohne on() verhält sich das Micro-Frontend exakt wie bisher —
// der Bus ist reine Beobachtung, kein Event ist Pflicht.
// on() liefert die Abmeldefunktion zurück; alternativ handle.off(name, fn).
const offCart = handle.on("cart:update", (cart: ShopCartUpdate) => {
  // Bei jeder Warenkorb-Änderung: Anzahl, Summe, Positionen (GA4-Item-Form).
  updateMiniCartBadge(cart.itemCount); // z. B. Badge im Seiten-Header
});

handle.on("checkout:complete", ({ order }) => {
  // Bestellung platziert — dieselbe aufgelöste Bestellung wie in onComplete.
  showConfirmationBanner(order.number);
});

handle.on("analytics", (event) => {
  // Spiegel des GA4-Funnels (identische Payloads wie config.onEvent).
  console.debug("Funnel:", event.name);
});

// Später gezielt abmelden — der Rest bleibt aktiv:
offCart();
حدثحرائق عندما
cart:updateتتغير سلة التسوق: تم إنشاؤها، أو إضافة العنصر أو إزالته، أو إفراغه. يحمل معرف سلة التسوق وitemCount والقيمة والعملة والخطوط كعناصر على شكل GA4.
checkout:completeتم تقديم الطلب — نفس اللحظة ونفس كائن الطلب الذي تم حله مثل config.onComplete.
analyticsنسخة مطابقة لمسار GA4 أعلاه: يتم أيضًا إصدار كل حمولة onEvent على الناقل، دون تغيير.

تحديث المستمعين على قيد الحياة ()

الاشتراكات مباشرة على المقبض، وليس على العرض: Handle.update({ ... }) يعيد عرض Micro-Frontend ولكنه يبقي كل مستمع متصلًا. يؤدي إلغاء التثبيت عبر Handle() إلى فصل جميع المستمعين تلقائيًا - ما عليك سوى إيقاف() (أو إلغاء الاشتراك المرتجع) عندما تريد التوقف عن الاستماع بينما يستمر المتجر في العمل.

معزول، لا يحجب أبدًا

يتم القبض على المستمع الذي يرمي وعزله - فهو لا يقطع تدفق المستخدم أبدًا ولا يؤثر أبدًا على المستمعين الآخرين. يعكس حدث "التحليلات" مسار تحويل GA4 بحمولات مماثلة لـ config.onEvent، بحيث يمكنك استهلاك مسار التحويل عبر الناقل أو رد الاتصال أو كليهما؛ تنطبق نفس قاعدة الموافقة: لا شيء يغادر الصفحة حتى يرسلها الكود الخاص بك.

publicKey مقابل sessionToken

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

publicKey — الثقة الأصل

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

sessionToken — هوية

تم سكه سرًا على الواجهة الخلفية لديك، وهو غير شفاف وقصير العمر. يجيب "من هو المستخدم؟" ترتبط الجلسة بالمفتاح العام الذي تم سكه من أجله: يجب أن يرسل طلب الجلسة أيضًا هذا المفتاح، ويتم رفض عدم التطابق (403). (publicKey)

قواعد الأمن

  • المفتاح السري موجود فقط على الخادم الخاص بك - ولا يوجد أبدًا في كود العميل أو الحزم أو ملفات env التي يتم شحنها إلى المتصفح.
  • احتفظ بالرمز في الذاكرة فقط. لا تستمر في تخزينه محليًا؛ قم بتحديثه عن طريق إعادة سك العملة. (localStorage)
  • الرموز قصيرة العمر (افتراضي 20 دقيقة، بحد أقصى 24 ساعة) ومستخدم واحد.
  • قم بإلغاء الجلسة عند تسجيل الخروج حتى لا يمكن إعادة تشغيل الرمز المميز المسرب.
  • قم دائمًا بإقران الجلسة بمفتاحها العام - يتم رفض المفتاح المفقود أو غير المتطابق (403). (publicKey)
  • تعتبر الجلسة السيئة أو منتهية الصلاحية بمثابة 401 صعبًا - ولا تعد بمثابة تراجع صامت للمستخدم التجريبي.

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

POST/api/shop/sessionالنعناع — حامل السر (Bearer)
DELETE/api/shop/session/:tokenإبطال - سر حامل (Bearer)

في هذا العرض التوضيحي، يتم تنفيذ عملية سك العملة عبر الوكيل في المسارات المذكورة أعلاه. في الإنتاج، يتم سك الجلسة في قلب ADITUS — العقد مطابق. كلا الاستدعاءين من خادم إلى خادم: تتم مصادقة الواجهة الخلفية الخاصة بك باستخدام سر النعناع الخاص بعميلك (الذي تم إنشاؤه في وحدة تحكم مسؤول ADITUS) كرمز مميز لحاملها. نعناع واحد لكل تسجيل دخول هو النمط العادي؛ الرمز المميز الناتج هو كل ما يراه المتصفح على الإطلاق. (Bearer)

يمكنك تجربة هذا التدفق الدقيق باستخدام بيانات الاعتماد الخاصة بك فياختبار التكامل— النعناع، ​​جبل، المشي في الرحلة.

سك جلسة - طلب

رأسقيمةمعنى
AuthorizationBearer <mint-secret>سر النعناع الخاص بعميلك، والذي تم إنشاؤه في وحدة تحكم المشرف الخاصة بـ ADITUS. من جانب الخادم فقط - يجب ألا يتم شحنه مطلقًا إلى متصفح أو حزمة تطبيقات.
Content-Typeapplication/jsonالجسم هو كائن JSON.
مجال الجسميكتبمعنى
publicKeystring · requiredالمفتاح القابل للنشر الذي تم سك الجلسة من أجله. يجب أن يكون هو نفس المفتاح الذي يتم تركيب الواجهة الأمامية الصغيرة به - يتم التحقق من كل مكالمة متجر لاحقة (403 عند عدم التطابق). يجب أن ينتمي سر حامل هذا المفتاح إلى عميل هذا المفتاح. (Bearer)
emailstring · optionalمستخدم المتجر الذي تعمل فيه الجلسة. يمكنك الحصول عليها من جلسة خادمك المصادق عليها (تسجيل الدخول/ملف تعريف الارتباط SSO أو JWT) - وليس من طلب المتصفح أبدًا، وإلا يمكن للزائر الحصول على جلسة شخص آخر. قم بحذفها لإنشاء جلسة مجهولة: يمكن للزائر تصفح عربة التسوق وملؤها، لكن الواجهة الأمامية الصغيرة تحظر الخطوة التي تلي عربة التسوق حتى يوفر رد الاتصال onUserRequired جلسة مرتبطة بالمستخدم (راجع تدفق المستخدم الاختياري). لا يزال يتم رفض البريد الإلكتروني الحالي ولكن المشوه (400).
externalUserIdstring · optionalمعرف المستخدم الخاص بك، المُخزن مع الجلسة كبيانات وصفية (مفيد للدعم وارتباط السجل). لم يتم تفسيره بواسطة ADITUS.
ttlSecondsnumber · optionalمدة الجلسة بالثواني. الافتراضي 20 دقيقة، بحد أقصى 24 ساعة. عند انتهاء صلاحيتها، تُرجع مكالمات المتجر 401 session_expired — ثم قم بصك رمز مميز جديد (على سبيل المثال عبر الخطاف onSessionExpired).
eventSlugstring · optionalيعمل على إصلاح جانب خادم ENTRY POINT الخاص بالرحلة: تعيين، تبدأ الواجهة الأمامية الصغيرة مباشرة عند تحديد مقالة هذا الحدث؛ تم حذفه، فهو يبدأ في نظرة عامة على الحدث. نظرًا لأنه جزء من الجلسة المسكوكة، فلا يمكن للمتصفح التعامل معه. الأحرف غير الصالحة تنتج 400 valid_event_slug؛ سبيكة ثابتة لا تحل أي حدث مباشر تعود إلى النظرة العامة على الحدث.

سك جلسة – الرد (200)

مجاليكتبمعنى
sessionTokenstringرمز غير شفاف (sess_...). قم بتسليمها إلى المتصفح وتمريرها إلى mount() كـ sessionToken؛ ترسلها الواجهة الأمامية الصغيرة كجلسة X-Aditus في كل مكالمة متجر. لا يحتوي على بيانات المستخدم ولا يمكن فك تشفيره.
expiresAtnumberانتهاء الصلاحية كطابع زمني لنظام Unix بالمللي ثانية. معلوماتية بحتة لجدولتك الخاصة - تتفاعل الواجهة الأمامية الصغيرة مع 401 من تلقاء نفسها.

إلغاء الجلسة

DELETE /api/shop/session/:token بنفس سر الحامل - يجب أن ينتمي السر إلى العميل الذي تم سك الجلسة من أجله. قم باستدعائه عند تسجيل الخروج حتى يموت الرمز المميز مع جلستك الخاصة. الاستجابة: { "تم إبطاله": صحيح } (أو خطأ إذا كانت الجلسة قد انتهت بالفعل). الإلغاء أمر غير فعال وآمن للحرق والنسيان. (Bearer)

الأخطاء واضحة

حالةشفرةمعنى
503session_not_configuredجلسات التضمين غير مهيأة لهذا العميل.
401unauthorizedقيمة mint secret المستخدمة لإنشاء الجلسة غير صالحة أو مفقودة.
400invalid_public_keyقيمة public key المقدمة غير صالحة.
400invalid_emailعنوان البريد الإلكتروني المقدم غير صالح.
400invalid_tokenقيمة session token المقدمة غير صالحة.
429rate_limitedعدد الطلبات كبير جداً. حاولوا مجدداً بعد قليل.
403public_key_invalidتنسيق public key المقدمة غير صالح.
403public_key_unknownلم يتم التعرف على public key المقدمة.
403public_key_origin_unresolvedتعذر تحديد مصدر هذا الطلب.
403public_key_domain_not_allowedالنطاق الذي صدر منه هذا الطلب غير مسموح له باستخدام public key هذه.
401invalid_sessionالجلسة المقدمة غير صالحة.
401session_expiredانتهت صلاحية الجلسة المقدمة.
403session_requires_public_keyتتطلب هذه الجلسة public key.
403session_key_mismatchلا تتطابق public key مع الجلسة.
403anonymous_sessionهذه العملية غير متاحة لجلسة مجهولة الهوية.

الآن اجعلها لك

تم تعيين الهوية - بعد ذلك، قم بوضع علامة تجارية على الواجهة الأمامية الصغيرة. تقوم أداة التصميم بإنشاء تكوين سمة جاهز للصقه وتشرح كل معلمة، مع معاينة مباشرة.

افتح أداة التصميم