تتبع الأحداث
الأحداث هي المادة الخام لكل شيء في Joryio: الشرائح ومشغلات الرحلات وفلاتر الحملات والتحليلات كلها تعمل على الأحداث التي ترسلها تطبيقاتك. يغطي هذا الدليل الاصطلاحات والآليات المشتركة بين كل SDKs: الويب وiOS وAndroid وReact Native. للتثبيت والإعداد الخاص بكل منصة، راجع صفحات SDK الفردية.
كيفية عمل التتبع
تتبع كل SDK المسار نفسه:
- تستدعي
track(eventName, properties)في تطبيقك. - تضع SDK الحدث محليًا في طابور (مع استمرار العمل دون اتصال) وترسله على دفعات، افتراضيًا كل 5 ثوانٍ أو كل 50 حدثًا، أيهما أولًا.
- تُسلّم الدفعة إلى
POST /v1/track/batch، بمصادقة مفتاح SDK لتطبيقك (التنسيقjry_sdk_<platform>_<random>، واحد لكل تطبيق؛ راجع نظرة عامة على التطبيقات). - يحل الخادم المستخدم (مجهولًا أو معرّفًا) ويخزن الأحداث ويوزعها إلى الشرائح ومشغلات الرحلات والتحليلات.
يمكن أن تحتوي الدفعة على 500 حدث على الأكثر. تضبط SDK الطوابع الزمنية عند وقت الاستدعاء؛ ويقبل الخادم ميلي ثانية epoch أو سلاسل ISO-8601 ويعود إلى وقت الخادم إذا كان الطابع الزمني مفقودًا أو غير صالح، فلا تؤدي ساعة جهاز خاطئة إلى رفض حدث. وترسلها كل حزم SDK بتوقيت UTC وبدقة الميلي ثانية (ISO-8601 مثل 2026-10-07T09:15:02.123Z، أو ميلي ثانية epoch)، فيحافظ استدعاءان في الثانية نفسها على ترتيبهما.
الجلسات
لا تبدأ الجلسة إلا عبر دورة الحياة العادية لـ SDK - عند انتقال التطبيق إلى الواجهة، أو عند تحميل صفحة على الويب - وهذا يرسل دائمًا حدث Session Start. أما قراءة الجلسة (معرّفها أو تفاصيلها) ومزامنة رسائل in-app فلا آثار جانبية لها: لا تبدأ جلسة ولا تمدّدها أبدًا، فلا يمكن لفحص الجلسة من شيفرتك أن ينشئ جلسات بلا Session Start.
اصطلاحات تسمية الأحداث
اسم الحدث سلسلة تصل إلى 255 حرفًا. لا تفرض Joryio تنسيقًا بعد ذلك، لكن الاتساق مهم لأن أسماء الأحداث هي كيفية العثور عليها لاحقًا في منشئي الشرائح ومشغلات الرحلات ومستكشف الأحداث.
توصيات:
- استخدم Title Case مع مسافات. تتبع أحداث Joryio المدمجة تصنيف أحداث التجارة الإلكترونية المعياري في المجال (الكائن + فعل بالماضي، Title Case):
Product ViewedوProduct AddedوCheckout StartedوOrder Completedمن تكاملات المتاجر، لذلك تبقي الأحداث المخصصة بـ Title Case مثلTrial StartedوSignup Completedالكتالوج موحدًا. تعمل snake case أيضًا، لكن اختر اصطلاحًا واحدًا والتزم به؛ فالمزج ينشئ أحداثًا تبدو مكررة. - سمّ الفعل لا واجهة المستخدم. يبقى
Order Completedصالحًا بعد إعادة تصميم؛ أماGreen Button Clickedفلا. - استخدم الكائن + فعلًا بالماضي. مثل
Subscription UpgradedوVideo PlayedوSearch Performed. - أبقِ التغير في الخصائص لا الأسماء. حدث
Product Viewedواحد له خاصيةcategoryأفضل من خمسين حدثViewed <Category>؛ تطابق الشرائح والمشغلات اسم الحدث أولًا ثم تصفي الخصائص. - تجنب الأحداث العامة جدًا. لا يخبرك
Clickedبلا خصائص بشيء يمكنك التصرف بناءً عليه.
أسماء الأحداث حساسة لحالة الأحرف: order_placed وOrder_Placed حدثان مختلفان.
أسماء الأحداث التي تبدأ بـ canvas. أو message. أو in_app. (بأي حالة أحرف) محجوزة لأحداث الرحلات والرسائل الخاصة بـ Joryio، ويُرفض أي حدث ترسله بمثل هذا الاسم - راجع أسماء الأحداث المحجوزة.
الخصائص وأنواع البيانات
الخصائص كائن JSON ملحق بكل حدث. تُقبل أي قيمة JSON:
| النوع | المثال | الملاحظات |
|---|---|---|
| سلسلة | "currency": "USD" | استخدمه أيضًا للتواريخ كسلاسل ISO-8601 |
| رقم | "total": 149.99 | أعداد صحيحة وعشرية |
| Boolean | "first_order": true | |
| مصفوفة | "item_ids": ["SKU-1", "SKU-2"] | |
| كائن | "shipping": { "method": "express" } | الكائنات المتداخلة مسموحة |
حدود الخادم لكل حدث:
| الحد | القيمة |
|---|---|
| طول اسم الحدث | 255 حرفًا |
| حجم الخصائص الإجمالي (JSON مسلّسل) | 50 KB |
| مفاتيح الخصائص العليا | 200 |
| عمق التداخل | 5 مستويات |
| الأحداث لكل طلب دفعة | 500 |
الحجم هو طول الخصائص مسلسلةً بصيغة JSON، بالأحرف (51,200)، ويحسب عمق التداخل كائن الخصائص نفسه المستوى 1. تنطبق الحدود على الخصائص التي يرسلها تطبيقك - خصائص $ التي يضيفها الخادم لا تُحتسب. الحدث الذي يتجاوز حدًا، أو اسمه فارغ أو طويل جدًا، يُرفض نهائيًا: تذكره استجابة الدفعة في rejectedIndices (راجع استجابات الدفعات) ويُخزَّن باقي الدفعة. تتحقق حزم SDK من الحدود نفسها عند استدعاء track() وتتخلص من مثل هذا الحدث هناك، مع سطر في السجل.
تُرفض الأحداث التي تتجاوز هذه الحدود. تتبع أسماء الخصائص النصيحة نفسها لأسماء الأحداث: اختر اصطلاحًا (product_id، لا productId أحيانًا) وحافظ على ثبات الأنواع؛ فـ order_id الذي يكون سلسلة في حدث ورقمًا في آخر يجعل التصفية غير موثوقة.
القيم NaN وInfinity و-Infinity ليست أرقام JSON. تُسقط كل حزمة SDK الخاصية أو السمة التي تكون قيمتها إحداها عند الاستدعاء، وتسجّل تحذيرًا، وترسل بقية الاستدعاء بدون ذلك المفتاح - لا يصل أي استثناء إلى شيفرتك، ولا يُخزَّن شيء على الجهاز، ولا تؤخّر قيمة خاطئة واحدة أي كتابات أخرى. وينطبق الأمر نفسه على الخصائص المخصصة في متتبعات التجارة الإلكترونية. وعلى الويب، تُسقط بالطريقة نفسها قيمة السمة التي لا يمكن تسلسلها (كائن دائري، أو BigInt، أو دالة).
تُضاف الخصائص التي تبدأ بـ $، مثل $platform و$session_id و$app_id، تلقائيًا: بعضها من SDKs ($device_id وبيانات الجهاز في Session Start) وبعضها من مسار إدخال Joryio عند استقبال الحدث ($app_id و$session_id و$is_identified). عامل هذه البادئة كمحجوزة ولا تستخدمها لخصائصك.
قواعد تسمية مفاتيح سمات المستخدم
لمفاتيح السمات (الكائن الذي تمرّره إلى setAttributes / setAttribute) قيدان
إضافيان، والمفتاح الذي يخالف أيًا منهما يُسقَط - وتُحفظ بقية الاستدعاء كالمعتاد:
| غير مسموح | السبب |
|---|---|
. في أي موضع من المفتاح | النقطة تُقرأ كفاصل مسار لا كحرف. فـ "profile.email" سيُخزَّن متداخلًا profile: { email }، ولن يطابق أي شريحة. |
$ في البداية | محجوز، تمامًا كخصائص الأحداث أعلاه. |
| مفتاح فارغ | لا شيء لتخزينه. |
تُسقَط المفاتيح ولا يُعاد تسميتها عمدًا: إعادة التسمية ستُبلغ عن نجاح بينما تضع بياناتك في مكان لا تستعلم عنه أبدًا.
identify مقابل track
أرسل أرقام الهاتف دائمًا بالصيغة الدولية الكاملة (E.164): + ثم رمز الدولة ثم الرقم - مثل +972501234567. الرقم الذي لا يبدأ بـ + يُقرأ وفق الدولة الافتراضية لأرقام الهاتف في مساحة العمل؛ وإذا لم تُعيَّن، أو أمكن قراءة الأرقام بطريقتين، يُحفظ غير موثّق ولا يُستخدم للرسائل. كيف تُعالَج أرقام الهاتف
يقوم الاستدعاءان الأساسيان بمهام مختلفة:
identify(userId)يقول من هو المستخدم. يربط الجهاز/الجلسة الحاليين بمعرّف المستخدم الثابت لديك ويدمج أي سجل مجهول في ذلك الملف. تصف السمات المضبوطة بـsetAttributesالمستخدم (Email والخطة والاسم) وتعيش في الملف الشخصي.track(eventName, properties)يقول ما الذي حدث. تصف الخصائص الحدث لا المستخدم، ولا تتغير بعد تسجيله.
قواعد عملية:
- استدعِ
identifyبمجرد معرفتك المستخدم، عند الدخول وعند بدء التطبيق إذا استعيدت جلسة. استخدم معرّف المستخدم نفسه في كل منصة حتى يصل نشاط الويب والهاتف إلى ملف واحد (راجع التتبع متعدد المنصات). - قبل
identify، تُتبع الأحداث بمعرّف مجهول. عند التعريف لاحقًا، يدمج الخادم السجل المجهول في الملف المعرّف، فلا تضيع أحداث ما قبل التسجيل (الزيارة الأولى والإسناد). - لا يوجد استدعاء منفصل
alias(userId): يربطidentify(userId)السجل المجهول ويسمّي الشخص معًا. لتسجيل معرّفات الشخص في أنظمة أخرى (معرّف CRM أو رقم ولاء) استخدمaddAlias(label, name)- يعمل قبل تسجيل الدخول أيضًا، وتنتقل الأسماء البديلة إلى الملف المعرّف عند تعريف الزائر. - ضع بيانات كل حدوث في خصائص الحدث (
totalوcoupon) والحقائق الدائمة عن الشخص في السمات (planوlifetime_value). - استدعِ
reset()عند تسجيل الخروج حتى لا يرث المستخدم التالي على الجهاز الملف الشخصي.
الحدث نفسه في كل SDK
استدعاء track متعمد أن يكون بالشكل نفسه في SDKs. إليك حدث Order Completed نفسه على المنصات الأربع.
- Web (JS)
- iOS (Swift)
- Android (Kotlin)
- React Native
import JoryioSDK from '@joryio/web-sdk';
const joryio = new JoryioSDK({ sdkKey: 'jry_sdk_web_...' });
joryio.track('Order Completed', {
orderId: 'ORD-2024-001',
total: 149.99,
currency: 'USD',
item_count: 3,
coupon: 'SAVE10',
});
Joryio.shared.track("Order Completed", properties: [
"orderId": "ORD-2024-001",
"total": 149.99,
"currency": "USD",
"item_count": 3,
"coupon": "SAVE10"
])
Joryio.track("Order Completed", mapOf(
"orderId" to "ORD-2024-001",
"total" to 149.99,
"currency" to "USD",
"item_count" to 3,
"coupon" to "SAVE10"
))
import Joryio from '@joryio/react-native-sdk';
Joryio.track('Order Completed', {
orderId: 'ORD-2024-001',
total: 149.99,
currency: 'USD',
item_count: 3,
coupon: 'SAVE10',
});
لأن الاسم والخصائص متطابقان، يطابق شرط شريحة أو مشغّل رحلة واحد الحدث مهما كانت المنصة التي أتى منها.
لنشاط التجارة الإلكترونية القياسي (مشاهدات المنتجات والسلات وإتمام الشراء والطلبات)، فضّل متتبعات التجارة الإلكترونية المدمجة في SDKs؛ فهي تصدر أسماء الأحداث المعيارية التي تتوقعها ميزات التجارة الإلكترونية في Joryio. راجع دليل تتبع التجارة الإلكترونية عبر SDKs.
ما يحدث على الخادم
بعد قبول دفعة، فإن كل حدث:
- يُخزن في مخزن التحليلات، ملحقًا بملف المستخدم المحلول (معرّف أو مجهول).
- يُقيّم مقابل مشغلات الرحلات. تسجل الرحلة التي يطابق مشغّل دخولها اسم الحدث (وفلاتر الخصائص) المستخدم فورًا؛ وهكذا تبدأ رحلات «السلة المتروكة» أو «الترحيب».
- يغذي الشرائح. تتحدث شروط الشرائح القائمة على الأحداث («نفذ
Order Completedفي آخر 30 يومًا») من تدفق الأحداث، وتلتقط الحملات التي تستهدف تلك الشرائح التغيير. - يظهر في التحليلات: مستكشف الأحداث والمسارات وتحليلات كل تطبيق.
- يمكن أن يحدّث الملف الشخصي. بعض الأحداث لها آثار جانبية يديرها الخادم؛ مثلًا، تحدّث أحداث
Session Startسجل جهاز المستخدم وتضبط سمات الملف مثلcountry(المشتق من IP الطلب).
سلوكان للخادم يجدر معرفتهما:
- تعليم الروبوتات. تُعلّم الطلبات من وكلاء مستخدم روبوت معروفين (تُوسم الأحداث وتستبعدها التحليلات)؛ وتتخطى استدعاءات تغيير الملف مثل
identifyوsetAttributesللروبوتات. لذلك قد لا تنشئ الزيارات من متصفحات عديمة الرأس في أتمتة اختباراتك ملفات شخصية. - تحقق مرن. تُزال الحقول الإضافية غير المعروفة من الحمولة بدل رفضها، فلا يسقط عدم تطابق إصدار SDK أحداثك.
عقد الخادم لطلبات SDK
يعتمد منطق الطابور وإعادة المحاولة في حزم SDK على سلوك الخادم الموضح أدناه. إذا كنت تستدعي هذه النقاط بنفسك (تكامل مخصص أو proxy)، فتعامل مع الاستجابات بالطريقة نفسها.
استجابات الدفعات
يقبل POST /v1/track/batch حتى 500 حدث وجسم طلب حتى 1 MB.
| الاستجابة | المعنى | ما يجب فعله |
|---|---|---|
200 مع success: true | تم تخزين كل الأحداث | احذف الدفعة |
200 مع success: false | لم يُخزَّن بعض الأحداث أو كلها. تذكر قائمتان مواقعها (بدءًا من 0) في الدفعة التي أرسلتها: failedIndices (فشل من جانبنا) وrejectedIndices (مرفوضة نهائيًا - تتجاوز حدود الحدث أو اسم غير صالح). وpartial: true يعني أن البقية خُزِّنت؛ ويحسب failed القائمتين معًا | أعد إرسال أحداث failedIndices فقط - إعادة إرسال حدث مخزَّن آمنة، يمنع clientEventId التكرار. تخلَّص من أحداث rejectedIndices: لن ينجح إرسالها مرة أخرى أبدًا |
400 | فشل الجسم في التحقق (مثلًا أكثر من 500 حدث، أو حدث تالف) | لا تعد إرساله كما هو - قسّم الدفعة |
413 | الجسم أكبر من 1 MB | قسّم الدفعة |
401 | مفتاح SDK غير صالح أو غير نشط، أو (مع تفعيل SDK Authentication) طلب يذكر userId ورمز X-Joryio-Auth فيه مفقود أو منتهي الصلاحية أو يخص مستخدمًا آخر - الجسم error: "sdk_authentication_error" وreason: missing أو expired أو invalid أو sub_mismatch (الرمز يخص مستخدمًا آخر). لا تحتاج الطلبات المجهولة إلى رمز | حدّث الرمز وأعد المحاولة. الأحداث المنتظرة لمستخدم لم يعد هو المسجَّل دخوله لا يمكن أن تحصل على رمز: عند أي reason (missing أو expired أو invalid أو sub_mismatch) تخلَّص منها. لا تصل إلى معالج أخطاء SDK Authentication إلا إخفاقات المستخدم المسجَّل دخوله حالياً، فلا تؤدي بقايا مستخدم سابق أبداً إلى تسجيل خروج من يستخدم التطبيق الآن |
402 مع code: "SDK_INGESTION_SUSPENDED" | استقبال الأحداث متوقف مؤقتًا للحساب | توقف عن الإرسال لهذه الجلسة، واحتفظ بالكتابات، ولا تحتسب 402 ضمن حد المحاولات الخاص بها؛ ويظل سحب الموافقة يُرسل، حتى عندما ينتظر في قائمة الانتظار خلف كتابة مرفوضة |
403 | رفضت سياسة الهوية في التطبيق الطلب: ترفض strict SDK identity الكتابات المجهولة (code: "SDK_STRICT_IDENTITY_REQUIRED")، وترفض SDK Authentication أي anonymousId ينتمي إلى مستخدم معرّف (code: "SDK_ANONYMOUS_ID_IDENTIFIED"). سحب الموافقة هو الاستثناء من strict SDK identity - انظر سحب الموافقة يُسلَّم دائماً | توقف عن الإرسال لهذه الجلسة |
429 | تجاوز حد المعدل. يحدد الترويسة Retry-After مدة الانتظار بالثواني (وكذلك retryAfter في الجسم) | انتظر ثم أعد المحاولة |
5xx | خطأ في الخادم | أعد المحاولة مع backoff |
يبدو الفشل الجزئي هكذا:
{ "success": false, "partial": true, "processed": 47, "failed": 3, "failedIndices": [3, 17], "rejectedIndices": [21], "error": "failed to track: 2 (retry), rejected: 1 (do not retry)" }
استجابات مسارات الكتابة الأخرى
ليس لـ /v1/identify و/v1/attributes و/v1/alias/add و/v1/subscriptions/channel و/v1/subscriptions/group و/v1/push/register و/v1/push/unregister نتيجة جزئية، لذا يوضح رمز الحالة وحده ما حدث. مسار الدفعات أعلاه وحده يجيب عن الفشل بـ 200.
| الاستجابة | المعنى | ما يجب فعله |
|---|---|---|
200 مع success: true | تم | أكّد الاستلام |
400 | الطلب خاطئ كما أُرسل - مثلًا بلا userId أو anonymousId، أو بلا سمات صالحة، أو web push بلا subscription، أو endpoint غير مسموح، أو تسجيل push على iOS لا يكون رمزه device token من APNs (رمز FCM مثلًا - سجّل رموز FCM على Android فقط) | لا تعد المحاولة - تخلَّ عنه وسجّله |
401 و402 و403 و429 | كما في جدول الدفعات أعلاه | كما أعلاه |
404 | الاشتراكات، في الخوادم الأقدم فقط: لا يوجد ملف بعد لهذا userId أو anonymousId. ينشئ الخادم الحالي الملف الناقص بدلًا من ذلك، كما يفعل /v1/alias/add | أعد المحاولة بعد إرسال الأحداث المنتظرة، لعدد محدود من المرات |
403 مع code: "verified_identity_required" | نقاط نهاية SDK فقط (/v1/subscriptions/channel، /v1/subscriptions/group): إعادة اشتراك شخص ألغى صراحةً (إلغاؤه أو من قائمة، STOP، إلغاء على بريده أو هاتفه - بما في ذلك من جهة اتصال أخرى) من جهة غير مُتحقق منها - userId بدون SDK Authentication، أو anonymousId يخص شخصًا معرّفًا. يُقبل الاشتراك الأول؛ والقائمة التي تتطلب التأكيد المزدوج تجيب pending: true وترسل بريد التأكيد. إلغاء الاشتراك مسموح دائمًا | لا تعد المحاولة - فعّل SDK Authentication، أو أعد الاشتراك من خادمك عبر الـ API |
409 مع code: "ALIAS_TAKEN" | /v1/alias/add فقط: ينتمي alias إلى ملف آخر | لا تعد المحاولة |
400 مع code: "ALIAS_LABEL_RESERVED" | /v1/alias/add وواجهة API الخاصة بـ aliases: التسميات shopify_customer وwoocommerce_customer وmagento_customer محجوزة - يكتبها تكامل المتجر | لا تعد المحاولة |
503 مع code: "SDK_TEMPORARILY_UNAVAILABLE" | فشل من جهتنا | احتفظ بالكتابة وأعد المحاولة مع backoff |
كيف تحتسب حزم SDK المحاولات
تحتسب كل حزم SDK المحاولات الفاشلة للكتابة بالطريقة نفسها. انقطاع الاتصال، ونفاد المهلة، و402 و429 و5xx لا تُحتسب أبدًا: تنتظر الكتابة وتُرسل بعد زوال السبب. ويُحتسب 404 (الخوادم الأقدم، لا يوجد ملف شخصي بعد) ضمن حد 5، وأي إجابة أخرى قابلة لإعادة المحاولة - 401 للمستخدم المسجّل دخوله، أو success: false - ضمن حد 10، تُسقط الكتابة بعده. أما 400 و403 و413 و422 فتُسقطها فورًا.
Aliases للزوار المجهولين
يقبل POST /v1/alias/add الجسم { "anonymousId", "userId", "aliasLabel", "aliasName" }، وuserId اختياري. بدون userId يُربط alias بالملف المجهول؛ وعندما يقوم الزائر بـ identify، ينتقل alias إلى الملف المعرَّف مع بقية السجل المجهول.
ينتمي alias (aliasLabel + aliasName) إلى ملف واحد في كل workspace. إذا كان ينتمي بالفعل إلى ملف آخر فلن يُنقل: تكون الاستجابة 409 مع code: "ALIAS_TAKEN"، ولن تفيد إعادة المحاولة. يعمل SDK Authentication هنا كما في كل مكان: ليس لدى الزائر المجهول token يقدمه، لذلك يُقبل addAlias المجهول؛ وبمجرد إرسال userId يجب أن يكون مرتبطًا بالـ token كما في identify. وحده Strict SDK identity يرفض الأسماء البديلة المجهولة. التسميات shopify_customer وwoocommerce_customer وmagento_customer محجوزة: فهي تحمل معرّفات العملاء لمتجر Shopify أو WooCommerce أو Magento متصل، ولا يكتبها إلا تكامل المتجر، لذلك يُرفض addAlias بإحداها مع 400 وcode: "ALIAS_LABEL_RESERVED".
الهوية وتتبع النقرات في رسائل In-App
يقبل /v1/in-app/sync و/v1/in-app/track الحقلين userId (بعد التعرف على الزائر) وanonymousId منفصلين. يقيّم الخادم الملف المعرَّف، وطالما أن identify لا يزال في الطريق، الملف المجهول. لا تضع أبدًا عنوان بريد إلكتروني في externalId.
تأتي قيمة action في /v1/in-app/track من مفردات ثابتة (displayed وclicked وdismissed وconverted وبعض المرادفات)؛ لا يُسجَّل الإجراء غير المعروف وتقول الاستجابة reason: "unknown_action". لتسجيل الزر أو الرابط الذي تم النقر عليه، أرسل اسمه في elementId (حتى 128 حرفًا) بجانب action: "clicked". يُخزَّن على الحدث in_app.clicked، ويجمّع تقرير النقرات في الحملة بحسبه.
مسارات تتبع Push
تُقدَّم إيصالات تسليم Push (POST /track/push/delivered/<trackingId>) والنقرات (GET /track/push/click/<trackingId>) من جذر مضيف API، وليس تحت /api. تبنيها حزم SDK من scheme وhost نقطة النهاية المضبوطة. تستجيب المسارات نفسها أيضًا تحت /api/track/push/...، لذا يستمر الإصدار الذي احتفظ بالمسار /api في الإبلاغ.
سجل إلغاء الاشتراك في التتبع
عندما يختار شخص إيقاف التتبع (optOut() في كل SDK)، تكتب حزمة SDK في الملف الشخصي السمة tracking_opted_out: true، ويعيدها optIn() إلى false. إنها سمة منطقية (boolean) مثل أي سمة أخرى: صفِّ بها في شريحة ("tracking_opted_out تساوي true") لإبعاد من أوقفوا التتبع عن جمهور ما، أو لعدّهم. إنها سجل للقرار وليست تطبيقه - تتوقف حزمة SDK عن الإرسال من تلقاء نفسها - وكانت إصدارات SDK قبل 1.3.0 ترسلها باسم $tracking_opted_out، ويخزنها الخادم الآن تحت المفتاح نفسه tracking_opted_out.
سحب الموافقة يُسلَّم دائماً
أربع كتابات لا تفعل إلا تقليل ما يُجمع أو يُرسل: اشتراك قناة يُضبط على unsubscribed، وunsubscribe من مجموعة، وسجل إلغاء التتبع tracking_opted_out: true، و/v1/push/unregister. تحتفظ حزم SDK بها في قائمة الانتظار وتعيد محاولتها عندما يكون الشخص قد ألغى الاشتراك في التتبع، وعندما تكون موافقة التتبع مرفوضة أو معلّقة، وبعد wipeData()؛ ويقبلها الخادم في الحالات نفسها:
- لا يرفضها أي فحص للموافقة أو لإلغاء التتبع على الخادم - فالشخص الذي ألغى التتبع يستطيع إلغاء اشتراكه.
- لا ترفضها strict SDK identity من زائر مجهول. الاتجاه المعاكس وحده - الاشتراك، أو
tracking_opted_out: false، أو السجل المرسل مع أي سمة أخرى - لا يزال يتطلب مستخدماً معرّفاً تم التحقق من رمزه. - الحساب الموقوف مؤقتًا لا يرفضها: ما دام استقبال الأحداث متوقفًا (
402،SDK_INGESTION_SUSPENDED) يظل الخادم يقبلها وتواصل حزم SDK إرسالها، فيُسجَّل رفض الشخص أيًا كانت حالة الفوترة في الحساب. - الرمز المنتهي الصلاحية يظل صالحًا لها. مع تفعيل SDK Authentication، يُقبل سحب الموافقة الذي يذكر
userIdبرمز ذلك المستخدم حتى 30 يومًا بعد انتهاء صلاحية الرمز. يُفحص التوقيع والموضوع (sub) كالمعتاد؛ ولا يُخفَّف إلا شرط انتهاء الصلاحية، ولهذه الكتابات الأربع فقط - وكل ما عداها يحتاج إلى رمز غير منتهي الصلاحية. وهذا ما يجعل تسجيل الخروج لا يُضيّع شيئًا: عندما يسجّل مستخدم خروجه (reset()) تحتفظ SDK برمزه - مشفّرًا حيث تتوفر للمنصة مساحة تخزين آمنة (iOS Keychain، والتفضيلات المشفّرة في Android؛ وعلى الويب local storage بجانب قائمة الانتظار) - مع كتاباته المنتظرة، وتحذفه عندما تُسلَّم آخرها أو تُسقط، فيصل إلغاء الاشتراك الذي نُقر قبيل تسجيل الخروج، حتى بعد إعادة تشغيل التطبيق. - تسجيل الدخول أثناء رفض التتبع: لا يرسل
identify(userId)أثناء رفض الموافقة أو إلغاء الشخص للتتبع أي شيء، ولا يتتبع شيئًا، ولا يكتب ملفًا شخصيًا، لكن SDK تحتفظ بـuserIdعلى الجهاز لغرض واحد - يحمله سحب الموافقة الذي يُجرى حينها، فيُلغى اشتراك الملف الشخصي للشخص نفسه لا ملف مجهول جديد. وعند منح الموافقة أو عودة الشخص إلى التتبع يُنفَّذidentifyالعادي. ويمحوهreset()وwipeData(). - لا يحتاج
/v1/push/unregisterإلى رمزX-Joryio-Authحتى عندما يذكر الجسمuserId: ما يُزال هو رمز push أو endpoint الخاص بـ web push، فيتوقف جهاز المستخدم الذي سجّل خروجه عن تلقي إشعارات push رغم زوال رمزه. - القاعدة الوحيدة التي تبقى سارية: مع تفعيل SDK Authentication، لا يستطيع
anonymousIdينتمي إلى مستخدم معرّف سحب موافقة ذلك المستخدم دون رمزه (403،SDK_ANONYMOUS_ID_IDENTIFIED). ولا تزال كتابة الاشتراك التي تذكرuserIdتحتاج إلى رمز ذلك المستخدم، كما في كل مكان.
عندما تمتلئ قائمة الانتظار على الجهاز، تُسقط أولًا أقدم كتابة ليست سحبًا للموافقة.
التحقق وتصحيح الأخطاء
تحقق من وصول حدث
- شغّل الحدث في تطبيقك.
- في لوحة تحكم Joryio، افتح التحليلات → مستكشف الأحداث. صفِّ باسم الحدث وستراه خلال ثوانٍ من دفع SDK للدفعة (فاصل الدفع الافتراضي: 5 ثوانٍ).
- لعرض كل تطبيق، افتح الإعدادات → التطبيقات وانقر عرض التحليلات في التطبيق؛ يعرض إجمالي الأحداث وطابع آخر حدث وأسماء أعلى الأحداث، ما يؤكد بسرعة ما إذا كان أي شيء من مفتاح SDK يصل.
إذا لم تظهر الأحداث
- افرض الدفع. الأحداث مجمعة؛ استدعِ
flush()، المتاح في كل SDK، للإرسال فورًا بدل انتظار المؤقت. - فعّل سجل التصحيح. تملك كل SDK خيار
enableDebugيسجل كل حدث في الطابور وكل طلب شبكة مع استجابته. - افحص مفتاح SDK. يجب أن يطابق منصة التطبيق:
jry_sdk_web_...لـ Web SDK وjry_sdk_ios_...لـ iOS وjry_sdk_android_...لـ Android. يبطل المفتاح المعاد إنشاؤه المفتاح القديم فورًا. تُبلغ Android SDK (بما في ذلك React Native وUnity على Android) الخادم بمنصتها، ويُرفض مفتاح تطبيق من منصة أخرى بـ401وcode: "SDK_KEY_PLATFORM_MISMATCH". - تحقق من نشاط التطبيق في الإعدادات → التطبيقات؛ تُرفض الأحداث المرسلة بمفتاح تطبيق معطل.
- راقب استجابة الشبكة. تتضمن استجابة الدفعة
successوعند الفشل الجزئيfailedIndices(لم تُخزَّن، وسيعاد إرسالها) وrejectedIndices(مرفوضة نهائيًا). الخصائص الكبيرة جدًا (أكثر من 50 KB أو 200 مفتاح أو عمق يزيد عن 5 مستويات) هي السبب المعتاد للأحداث المرفوضة. - الويب فقط: تسمح نقاط نهاية تتبع SDK بأي أصل (
Access-Control-Allow-Origin: *)، لذلك تعني أخطاء CORS غالبًاapiEndpointخاطئًا أو إضافة متصفح حاجبة؛ افحص وحدة التحكم. أكد أيضًا أن الموقع يعمل بـ HTTPS ليستمر طابور localStorage. - تختبر بالأتمتة؟ تذكر تعليم الروبوتات أعلاه؛ تحقق بمتصفح أو جهاز حقيقي.