تخطّ إلى المحتوى الرئيسي

تكامل Web SDK

يتيح لك Joryio Web SDK تتبع سلوك المستخدمين وإرسال الأحداث وإدارة ملفات المستخدمين على موقعك.

التثبيت​

عبر npm أو yarn​

npm install @joryio/web-sdk
# or
yarn add @joryio/web-sdk

عبر CDN​

تُخدم ملفات SDK من مضيف عالمي واحد، https://sdk.joryio.com، أياً كانت المنطقة التي توجد فيها مساحة عملك. تبقى الأحداث واستدعاءات API متجهة إلى مضيف API الخاص بمنطقتك (الإعداد apiEndpoint) - موقع الملف وحده هو العالمي.

<!-- Recommended: the newest 1.x. Picks up fixes, never a breaking change. -->
<script src="https://sdk.joryio.com/web/1/joryio.min.js"></script>

<!-- Pinned: one exact version that never changes. You upgrade by changing the URL. -->
<script src="https://sdk.joryio.com/web/1.3.3/joryio.min.js"></script>
المسارما يقدمه
/web/1/<file>أحدث إصدار 1.x (موصى به)
/web/<version>/<file>إصدار محدد واحد، مثل /web/1.3.3/ - لا يتغير محتواه أبداً

الملفات: joryio.min.js (SDK)، وjoryio-sw.js (service worker الخاص بإشعارات الويب)، وjoryio.umd.js (بناء UMD غير مصغّر).

متى تستخدم كل خيار
  • /web/1/: لمعظم المواقع. تصل إصلاحات الأخطاء وميزات 1.x الجديدة تلقائياً، ولا يصل أي تغيير كاسر (2.0) عبر هذا المسار أبداً.
  • المثبت، /web/1.3.3/: للمواقع التي تريد تحكماً حتمياً في وقت ترقية SDK. لا يتغير الملف أبداً، لذلك تخزنه المتصفحات مؤقتاً لمدة طويلة.

ترقية تكامل مثبت. عندما يصدر Joryio نسخة SDK جديدة، غيّر الرقم في المقتطف:

<!-- Before -->
<script src="https://sdk.joryio.com/web/1.3.3/joryio.min.js"></script>
<!-- After -->
<script src="https://sdk.joryio.com/web/1.4.0/joryio.min.js"></script>

يعامل المتصفح URL الجديد كملف جديد ويشغّل SDK الجديد.

انتقل من مضيف API

حتى أكتوبر 2026 كان ملف SDK يُخدم من مضيف API على https://api-eu1.joryio.com/sdk/web/.... هذا المسار لم يعد موجوداً - استبدله بـ https://sdk.joryio.com/web/1/.

الشيفرة غير المتزامنة (snippet)​

الصق هذه الشيفرة في <head>. تحمّل SDK دون أن تعطل الصفحة، والاستدعاءات التي تتم قبل تحميل الملف توضع في طابور وتُنفذ بالترتيب. يمكن استدعاء كل طريقة عامة بهذه الطريقة، ويمكن أن يأتي init نفسه لاحقاً - مثلاً من callback شريط الموافقة.

<script>
!function(){var e=window.joryio=window.joryio||[];if(!e.initialize)if(e.invoked)window.console&&console.error&&console.error("Joryio snippet included twice.");else{e.invoked=!0,e.methods=["init","track","identify","addAlias","setAttributes","setAttribute","incrementAttribute","unsetAttribute","addToArray","removeFromArray","reset","flush","optOut","optIn","updateConsent","getTrackingConsent","isUserOptedOut","setSdkAuthenticationToken","setSdkAuthenticationErrorCallback","getAnonymousId","getUserId","ready","getIdentity","getSessionInfo","getUTMData","updateUTM","syncInAppCampaigns","getInAppCampaigns","displayInAppCampaign","dismissInAppMessage","trackInAppConversion","ecommerce","wipeData","destroy"],e.keep=["init","optOut","optIn","updateConsent","wipeData"],e.factory=function(t){return function(){var n=Array.prototype.slice.call(arguments);if(n.unshift(t),e.length>=1e3)for(var i=0;i<e.length;i++)if(e.keep.indexOf(e[i][0])<0){e.splice(i,1);break}return e.push(n),/^(get|is)[A-Z]/.test(t)?void 0:e}};for(var t=0;t<e.methods.length;t++){var n=e.methods[t];e[n]=e.factory(n)}e.load=function(e){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src="https://sdk.joryio.com/web/1/joryio.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(t,n)},e.load()}}();
joryio.init({ sdkKey: 'jry_sdk_web_YOUR_KEY' });
</script>
  • يتسع الطابور لـ 1,000 استدعاء على الأكثر. بعد ذلك تُحذف الأقدم - وليس أبداً init أو optOut() أو optIn() أو updateConsent() أو wipeData().
  • لا يمكن لاستدعاء في الطابور أن يعيد قيمة. دالة قراءة مثل getUserId() أو isUserOptedOut() تعيد undefined حتى يكتمل تحميل SDK. لقراءة معرّف، أو لاستخدام user أو push أو المتتبع الذي يعيده ecommerce()، استخدم ready: joryio.ready(function (sdk) { sdk.getAnonymousId(); }).
  • فشل init لا يضيّع شيئاً. إذا فشل init (مفتاح SDK مفقود أو غير صالح - ويُسجَّل السبب في وحدة التحكم)، يبقى window.joryio هو الطابور. استدعِ joryio.init({...}) مرة أخرى بالمفتاح الصحيح فيبدأ SDK بكل الاستدعاءات التي جرت حتى الآن - قرارات الخصوصية أولاً، ثم الباقي بالترتيب، بما في ذلك إلغاء الاشتراك.
  • الاستدعاءات التي تمت والتتبع متوقف تُسقط. يُسقط أي track() أو كتابة سمة أو addAlias() أو trackInAppConversion() موضوع في الطابور بينما كان الزائر قد ألغى التتبع أو كانت الموافقة مرفوضة - وفق الاختيار المحفوظ أو إعدادات init أو قرار موضوع في الطابور قبله - ولا يعيده optIn() أو updateConsent('granted') لاحق في الطابور. ويُحتفظ بـ identify() وبإلغاءات الاشتراك. وينطبق الأمر نفسه على استدعاء موضوع في الطابور قبل optOut() أو updateConsent('denied') في الطابور، أو قبل wipeData() موضوع في الطابور قبل init. أما دالة ready() الموضوعة في الطابور والتتبع متوقف، أو قبل optOut() أو updateConsent('denied') في الطابور، فتُنفَّذ، لكن استدعاءات track() وكتابات السمات والأسماء البديلة والتحويلات والاشتراكات التي تجريها أثناء تنفيذها تُسقط أيضاً - ما لم ترفع الدالة نفسها ما أوقف التتبع، لبقية تنفيذها: optIn() الخاص بها يرفع إلغاء التتبع، وupdateConsent('granted') الخاص بها يرفع رفض الموافقة، ولا يرفع أيٌّ منهما الآخر، ولا شيء داخلها يرفع optOut() أو رفضاً موضوعاً في الطابور بعدها. ولا يقيّد wipeData() موضوع في الطابور قبل init دالة ready() موضوعة في الطابور قبله (تُنفَّذ الدالة للزائر الجديد). وتعمل القراءات وإلغاءات الاشتراك، ويتصرف مثيل SDK الذي تسلّمته بشكل طبيعي بعد ذلك.
  • قرارات الخصوصية تُنفذ أولاً. يُطبَّق optOut() أو optIn() أو updateConsent() الموجود في الطابور، وwipeData() الموضوع في الطابور قبل init، داخل init قبل أن يرسل أي شيء - فلا يخرج أي Session Start أو مزامنة رسائل داخل التطبيق أو تسجيل إشعارات في الحالة التي تحل محلها:
<script>
/* the snippet above, then: */
joryio.updateConsent('denied'); // applied inside init, before anything is sent
joryio.init({ sdkKey: 'jry_sdk_web_YOUR_KEY' });
</script>

البدء السريع​

1. تهيئة SDK​

هيئ SDK أولاً بمفتاح SDK الخاص بك. تجده في لوحة Joryio ضمن الإعدادات ← التطبيقات.

import JoryioSDK from '@joryio/web-sdk';

// Initialize the SDK
const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_your_sdk_key_here',
enableDebug: false, // Enable debug logging in development
});

تحتوي الحزمة على خريطة exports: نسخة ES module لـ import ونسخة CommonJS لـ require. وتستخدم أدوات التجميع (webpack وesbuild وRollup وVite) نسخة ES module للحالتين، فالتطبيق الذي يستورد الـ SDK في ملف ويستدعيه بـ require في ملف آخر يشغّل SDK واحداً فقط. الاستيراد الافتراضي هو الصنف نفسه في أدوات التجميع وفي Node ESM (SSR وVitest وسكربتات .mjs)، ويعمل import { JoryioSDK } from '@joryio/web-sdk' أيضاً، وتعطيه TypeScript النوع نفسه مع moduleResolution بقيمة node16 أو nodenext أو bundler.

عند التحميل من CDN بدلاً من npm، تتوفر الفئة نفسها كـ window.Joryio:

const joryio = new Joryio({
sdkKey: 'jry_sdk_web_your_sdk_key_here',
});

2. تعريف المستخدمين​

أرسل أرقام الهاتف مع + ورمز الدولة

أرسل أرقام الهاتف دائمًا بالصيغة الدولية الكاملة (E.164): + ثم رمز الدولة ثم الرقم - مثل +972501234567. الرقم الذي لا يبدأ بـ + يُقرأ وفق الدولة الافتراضية لأرقام الهاتف في مساحة العمل؛ وإذا لم تُعيَّن، أو أمكن قراءة الأرقام بطريقتين، يُحفظ غير موثّق ولا يُستخدم للرسائل. كيف تُعالَج أرقام الهاتف

عرّف المستخدمين عند التسجيل أو تسجيل الدخول:

// Identify a user
joryio.identify('user_123');

// Set profile attributes separately
joryio.setAttributes({
firstName: 'John',
lastName: 'Doe',
plan: 'premium',
signupDate: '2024-01-15',
});

3. تتبع الأحداث​

تتبع إجراءات المستخدمين وسلوكهم:

// Track a custom event
joryio.track('Product Viewed', {
product_id: 'prod_123',
product_name: 'Premium Plan',
price: 99.99,
currency: 'USD',
});

// Track page views
joryio.track('Page Viewed', {
page: '/pricing',
title: 'Pricing Page',
category: 'Marketing',
});

خيارات الإعداد​

الخيارالنوعالافتراضيالوصف
sdkKeystringمطلوبمفتاح SDK من لوحة Joryio
apiEndpointstringhttps://api-eu1.joryio.com/apiخادم API في منطقتك. يكفي اسم الخادم وحده؛ تُضاف /api تلقائياً
userIdstring-البدء بمعرّف مستخدم معروف
anonymousIdstringيُولَّد تلقائياًاستخدام معرّف مجهول خاص بك
enableDebugbooleanfalseتفعيل سجل التصحيح في وحدة التحكم (logLevel: 'debug'). قيمة logLevel المضبوطة صراحةً هي التي تسري.
batchFlushIntervalnumber5000وتيرة إرسال الأحداث المجمعة إلى الخادم بالميلي ثانية. توضع محلياً وتُرسل كل 5 ثوان لتقليل طلبات الشبكة. القيم الأقل من 1000 تُرفع إلى 1000.
batchSizenumber50عدد الأحداث في كل طلب، وحجم القائمة الذي يطلق الإرسال قبل المؤقت. لا يتجاوز أبداً 500 حدث في الطلب (حد API)
sendImmediatelybooleanfalseإرسال كل حدث فوراً من دون تجميع، ولا يوصى به للإنتاج.
sessionTimeoutnumber1800000انتهاء الجلسة بالميلي ثانية، الافتراضي 30 دقيقة. تبدأ جلسة جديدة بعد هذه المدة من عدم النشاط - ويُتحقق من ذلك في كل track() وعندما يعود التبويب مرئياً.
trackSessionStartbooleantrueحدث Session Start واحد لكل جلسة جديدة. إعادة التحميل التي تستأنف الجلسة لا ترسل أي حدث منه.
trackPageViewsbooleanfalseتتبع Page Viewed تلقائياً عند تحميل الصفحة.
captureUTMbooleantrueالتقاط معاملات UTM تلقائياً من URL لإسناد الحملة.
resetSessionOnNewCampaignbooleanfalseبدء جلسة جديدة عند تغير UTM، وهو مفيد لتحليلات الجلسات لكل حملة.
trackDevicePropertiesbooleantrueتضمين معلومات الجهاز في Session Start.
persistQueuebooleantrueحفظ الأحداث المنتظرة في localStorage لتنجو من إعادة تحميل الصفحة.
maxQueueSizenumber1000أقصى عدد من الأحداث تحتفظ به القائمة المحلية بينما يفشل الإرسال؛ بعده تُحذف الأقدم (مع تحذير في السجل عند تفعيل التسجيل)
maxRetriesnumber3إعادات فورية للطلب الواحد (5xx أو 429 أو خطأ شبكة). الدفعة التي تظل فاشلة تُحفظ ويعاد إرسالها لاحقاً
retryBackoffMsnumber1000الانتظار الأول قبل إعادة المحاولة بالميلي ثانية؛ يتضاعف مع كل فشل حتى 5 دقائق
requestTimeoutnumber10000المهلة بالمللي ثانية لكل طلب API (الأحداث، السمات، in-app، الإشعارات)
trackPagePropertiesbooleantrueإضافة المُحيل وصفحة الهبوط إلى أحداث "Page Viewed"
respectDoNotTrackbooleantrueاعتبار إعداد Do Not Track في المتصفح إيقافاً للتتبع
logLevelstring-'error' أو 'warn' أو 'info' أو 'debug'. لا يُسجَّل شيء ما لم يُضبط هذا الخيار أو enableDebug
trackingConsentstringغير محدد'granted' أو 'denied' أو 'pending'. اتركه غير محدد إن لم يكن في موقعك شريط موافقة. راجع الخصوصية والموافقة
optOutbooleanfalsetrue يوقف تتبع الزائر ويُحفظ بين تحميلات الصفحة، مثل optOut(). أما false فلا يلغي إيقافاً محفوظاً - وحده optIn() يفعل ذلك
enableSdkAuthenticationbooleanfalseإرفاق JWT يصدره خادمك وتحديثه. راجع مصادقة SDK
sdkAuthenticationTokenstring-أول JWT، يُستخدم عندما تكون قيمة enableSdkAuthentication هي true
pluginsarray[]إضافات مع beforeTrack / afterTrack (للويب فقط)
inApp.enabledbooleantrueتشغيل رسائل in-app أو إيقافها
inApp.evaluationModestring'differential-sync'متى تُجلب حملات in-app: 'differential-sync' (عند التحميل، وعند identify() وreset()، وفي جلسة جديدة، وعند تغيّر سمة مراقَبة، وكل inApp.syncInterval) أو 'session-start-only' (عند التحميل وidentify() وreset() والجلسة الجديدة فقط)
inApp.syncIntervalnumber0 (متوقف)إعادة جلب حملات in-app كل N مللي ثانية في وضع 'differential-sync'. القيم الأقل من 30000 تُرفع إلى 30000، ويتوقف أثناء إخفاء علامة التبويب
inApp.allowHtmlJsInAppMessagesbooleanfalseيسمح برسائل in-app بصيغة HTML. تُعرض الرسالة في iframe معزول تحت Content-Security-Policy صارمة: يعمل فقط برنامج الجسر النصي الخاص بالـ SDK - لا يعمل <script> الخاص بك ولا معالجات on* المضمّنة ولا <iframe> - لذا تعمل الأزرار والنماذج عبر data-joryio-action / data-jry-form. يتطلب التفعيل لأنه يعرض HTML مكتوبًا. لا يمكن للرسالة أبداً أن تنقل الإطار الخاص بها إلى صفحة أخرى: تمر الروابط وإعادات التوجيه عبر SDK الذي يفتحها في صفحتك (روابط http(s) والروابط النسبية إلى الجذر فقط)؛ وإن انتقل الإطار رغم ذلك تُغلق الرسالة. الرسائل الأصلية تُعرض في كل الأحوال. رسائل In-app
inApp.displayOptions.defaultPositionstring'center''top' أو 'center' أو 'bottom': موضع رسالة من نوع Custom. تحتفظ رسائل modal وbanner وslide-up وملء الشاشة بموضعها الخاص
inApp.displayOptions.defaultAnimationstring'fade''fade' أو 'slide' أو 'none': طريقة ظهور الرسائل. تُتجاهل الحركة عندما يطلب نظام الزائر تقليلها
inApp.displayOptions.backdropColorstring'rgba(0, 0, 0, 0.5)'لون الطبقة المعتمة خلف رسائل modal وملء الشاشة
inApp.displayOptions.closeButtonColorstringلون نص الرسالةلون زر الإغلاق
inApp.displayOptions.zIndexnumber999999ترتيب طبقات الرسائل (الطبقة المعتمة تحتها بدرجة واحدة)
inApp.onMessageDisplay / onMessageClick / onMessageDismissfunction-دوال لكل مرحلة - راجع Callbacks

تجميع الأحداث وتفريغها​

تجمع الأحداث محلياً وترسل إلى الخادم في مجموعات لتحسين استخدام الشبكة:

  • تفريغ تلقائي: كل batchFlushInterval ميلي ثانية، الافتراضي 5 ثوان.
  • تفريغ بالحجم: فور تراكم batchSize أحداث، الافتراضي 50.
  • تفريغ يدوي: استدعِ joryio.flush() لإرسال الأحداث المنتظرة فوراً.
  • عند مغادرة الصفحة: تُسلَّم الأحداث المنتظرة إلى المتصفح عبر fetch(..., { keepalive: true }) - دون ملفات تعريف الارتباط، ومع ترويسة مصادقة SDK. يُسلَّم فقط ما يتسع له حد keepalive في المتصفح (64 KB) - ميزانية واحدة للصفحة كلها مهما تعددت أحداث المغادرة - ويبقى الباقي في القائمة لعرض الصفحة التالي. وحين يكون المتصفح غير متصل، أو بعد فشل آخر إرسال، لا يُسلَّم شيء: تبقى الأحداث في القائمة بدلاً من أن تخرج في طلب يُرجَّح أن يفشل.
  • الإرسال الفاشل لا يوقف القائمة: يحمل كل طلب batchSize حدثاً على الأكثر (ولا يتجاوز أبداً 500 حدث أو نحو 900 KB). الطلب الذي يرفضه API لأنه غير صالح (400 أو 413 أو 422) يُقسَّم حتى يُحذف الحدث المعيب وحده؛ أما أخطاء الشبكة و429 و5xx فتحتفظ بكل الأحداث وتعيد المحاولة بتأخير متزايد؛ ورمز 402 (استقبال الأحداث متوقف لمساحة عملك) أو 403 أو مفتاح غير صالح يوقف إرسال الأحداث حتى نهاية عرض الصفحة ويحتفظ بها. وبعد 402 تنتظر كتابات الملف الشخصي أيضاً - باستثناء طلبات السحب، التي ما زال API يقبلها وما زالت تُرسَل.
  • الطوابع الزمنية: تحمل الأحداث طابعاً زمنياً بصيغة ISO 8601 بتوقيت UTC مع أجزاء الألف من الثانية (2026-10-07T09:30:00.123Z)، كما في كل SDK من Joryio.
  • success: false فشل: الاستجابة 2xx التي يقول جسمها success: false - لدفعة أو identify أو سمات أو اسم مستعار أو تغيير اشتراك أو تسجيل إشعارات - يعاد إرسالها ولا تُعدّ منجزة أبداً. وفي الدفعة لا يُحتفظ إلا بالأحداث التي يذكرها API في failedIndices.
  • الرفض النهائي يعني الحذف: الأحداث التي يذكرها API في rejectedIndices تُحذف ولا يعاد إرسالها. ويحذف track() الحدث قبل وضعه في الطابور (مع سطر في السجل) إن لم يكن طول اسمه بين 1 و255 حرفاً أو تجاوزت خصائصه حدود API: أكثر من 50 KB بعد التسلسل، أو أكثر من 200 مفتاح، أو تداخل أعمق من 5 مستويات.
  • مستخدم واحد لكل طلب: لا يخلط الطلب أبداً أحداث مستخدمَين، فلا تعطل أحداث مستخدم سجّل خروجه أحداث المستخدم التالي (راجع مصادقة SDK).
  • كتابات الملف الشخصي دائمة ومرتبة: كتابات السمات وaddAlias() وتغييرات الاشتراكات ومجموعات الاشتراك تُحفظ في localStorage حتى يؤكدها API، فلا يفقدها الانتقال إلى صفحة أخرى أو إغلاق علامة التبويب، وعند مغادرة الصفحة تُسلَّم المنتظرة منها أيضاً إلى fetch(..., { keepalive: true }). والتغيير الأحدث على القناة أو المجموعة أو السمة نفسها يحل محل تغيير أقدم ما زال منتظراً، فلا يصل تغيير أقدم أبداً بعد تغيير أحدث.
  • قاعدة إعادة محاولة واحدة لكل كتابة (بما فيها identify()): أخطاء الشبكة وانتهاء المهلة و429 و5xx و402 لا تُحتسب ضمنها أبداً ويعاد إرسالها بتأخير متزايد؛ ويعاد إرسال 404 ("لا ملف شخصي بعد") 5 مرات على الأكثر، وأي رفض آخر قابل لإعادة المحاولة (success: false، أو 401 لمصادقة SDK للمستخدم الحالي) 10 مرات على الأكثر؛ أما 400 و403 و409 و413 و422 فتحذف الكتابة مع سطر في السجل.
  • حد قائمة الانتظار: تحتفظ القائمة المحلية بحد أقصى maxQueueSize حدث (الافتراضي 1000). إذا تجاوزته، مثل فترات طويلة بلا اتصال، تُحذف الأقدم (مع تحذير في السجل عند تفعيل التسجيل).
// Send events immediately instead of batching
const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_...',
sendImmediately: true // Send each event immediately
});

// Or configure batching behavior
const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_...',
batchFlushInterval: 10000, // Flush every 10 seconds
batchSize: 20 // Or when 20 events accumulate
});

// Or manually flush at any time
joryio.track('Important Event', {...});
joryio.flush(); // Send now

إدارة الجلسات​

تتبع الجلسات نشاط المستخدم المتواصل وتُدار تلقائياً:

  • انتهاء الجلسة: 30 دقيقة من عدم النشاط افتراضياً، ويمكن ضبطه عبر sessionTimeout.
  • تبدأ جلسة جديدة عندما:
    1. يحمل المستخدم الصفحة للمرة الأولى.
    2. تمر مدة انتهاء الجلسة بلا أحداث.
    3. يستدعي المستخدم joryio.reset()، مثل تسجيل الخروج.
    4. يُكتشف Campaign جديد إذا كان resetSessionOnNewCampaign مفعلاً.

ضبط مهلة الجلسة:

const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_...',
sessionTimeout: 3600000 // 1 hour in milliseconds
});

// Or shorter session timeout
const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_...',
sessionTimeout: 600000 // 10 minutes
});
نصيحة

تُدار الجلسات تلقائياً بناءً على نشاط المستخدم؛ كل حدث متتبع يعيد ضبط مؤقت عدم النشاط.

تتبع Session Start​

يتتبع SDK افتراضياً حدث Session Start كلما بدأت جلسة جديدة. هذا الحدث:

  • يمكن استخدامه كمحفز في الحملات ومسارات الرحلات.
  • يحمل كل سياق الجلسة: معاملات UTM والمرجع وصفحة الهبوط وبيانات الجهاز عند تفعيل trackDeviceProperties.
  • يُخزن في ملف المستخدم كأي حدث، لذا تستطيع الشرائح والتحليلات عد الجلسات لكل مستخدم.

مثال لحدث بداية جلسة:

// Automatically tracked when user visits your site
{
event: "Session Start",
properties: {
utm_source: "google", // If UTM parameters present
utm_medium: "cpc",
utm_campaign: "spring_sale",
referrer: "https://google.com",
landing_page: "https://example.com/..."
},
userId: "user_123", // If identified
anonymousId: "anon_456",
sessionId: "sess_789"
}

بيانات الجلسة التلقائية​

يثري Web SDK أحداث Session Start ببيانات الجهاز والبيئة:

  • $user_agent
  • $timezone
  • $screen_width / $screen_height
  • $viewport_width / $viewport_height
  • $language / $languages
  • $platform
  • $browser
  • $device_id
  • country، رمز ISO-3166-1 alpha-2 مستخرج من IP عند بدء الجلسة.

حالات استخدام:

  1. رحلات الترحيب: استخدم Session Start محفزاً للرحلة للوصول إلى المستخدمين عند وصولهم إلى موقعك.
  2. شرائح الجلسات: تخزن أحداث Session Start لكل مستخدم، فتعدها شروط الشرائح القائمة على الأحداث؛ مثلاً «نفذ Session Start 10 مرات على الأقل» للمستخدمين المتقدمين أو «لم ينفذها في آخر 7 أيام» لإعادة التفاعل.
  3. إسناد الحملات: صفِّ التحليلات على Session Start وجمّعها حسب utm_campaign لمعرفة الحملات التي تقود أكبر عدد من الجلسات.

إيقاف تتبع بداية الجلسة:

const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_...',
trackSessionStart: false // Disable automatic session start events
});

رسائل In-app​

تعرض حزمة SDK رسائل داخل الصفحة نيابةً عنك. تظهر الحملات المؤهلة من تلقاء نفسها، وتُقاس مرات الظهور والنقرات والإغلاق تلقائيًا.

رموز التسليم (delivery tokens)​

عندما يقدّم الخادم حملة مؤهّلة، فإنه يصدر لها أيضًا رمز تسليم موقّعًا وقصير الأجل. يعيد الـ SDK إرسال هذا الرمز عند الإبلاغ عن ظهور أو نقرة أو إغلاق، ويتحقق الخادم من التوقيع قبل تسجيل أي شيء.

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

POST /v1/in-app/track (no deliveryToken)
{ "success": false, "error": "A delivery token is required" }

أما الرمز الموجود لكنه لا يجتاز التحقق - مزوّر أو معدّل أو منتهي الصلاحية - فيُرفض دائمًا أيًا كان الإعداد.

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

المواقع التي تحمّل الحزمة المستضافة من https://sdk.joryio.com/web/1/joryio.min.js تحصل على ذلك تلقائيًا.

نوعان من المحتوى​

المحتوىما هوكيف يُعرض
الأصلي (Native)بيانات منظمة - عنوان ونص وصورة وأزرارعناصر DOM عادية تُدرج كعُقد نصية. بدون iframe ودون تنفيذ أي script.
HTMLHTML و‑CSS كتبها المؤلف، تُحفظ كما كُتبت (تُزال فقط <base> و<meta http-equiv="refresh">)iframe معزول داخل صفحتك، تحت Content-Security-Policy جديدة لكل رسالة، لا يعمل فيه إلا سكربت الجسر الخاص بـ SDK.

السماح برسائل HTML​

رسائل HTML معطلة افتراضيًا. لا تعمل سكربتاتك فيها: يحفظ Joryio الترميز كما كُتب، لكن كل رسالة تحصل على Content-Security-Policy جديدة مع nonce جديد، فلا يعمل إلا سكربت الجسر الخاص بـ SDK - تبقى وسوم <script> ومعالجات on* التي كتبها المؤلف معطلة، ولا يستطيع الإطار إجراء اتصالات شبكة (connect-src 'none') ولا إرسال نماذج (form-action 'none'). لكن هذا الإطار يستطيع تنفيذ سكربت ضمن أصل (origin) صفحتك، فيبقى خطر وجود ثغرة في هذه السياسة، مثل متصفح لا يطبقها - ولهذا فإن تفعيلها قرار يتخذه فريقك:

const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_YOUR_KEY',
inApp: {
allowHtmlJsInAppMessages: true, // الافتراضي: false
},
});

تركه معطلًا لا يعطل الرسائل داخل الصفحة. تستمر الرسائل الأصلية في الظهور، لأنها بيانات تُكتب في عُقد نصية داخل DOM - دون أي مفسّر. تُتجاهل حملات HTML (مع تحذير في السجل عند تفعيل التسجيل). الحملة المتجاهلة لا تستهلك ظهوراً وتبقى مؤهلة.

إذا كانت سياسة أمان المحتوى (CSP) لديك تمنع السكربتات المضمّنة أو المحتوى داخل iframe، فاترك الإعداد معطلًا وأنشئ حملاتك كرسائل أصلية.

تنسيق الرسائل الأصلية من CSS الخاص بك​

الرسالة الأصلية هي DOM حقيقي داخل صفحتك، لا iframe، فيمكنك تنسيقها مثل أي عنصر تملكه. ويصدر المُصيِّر نقاط ارتساء ثابتة:

.joryio-inapp-native /* البطاقة */
.joryio-inapp-native h2 /* العنوان */
.joryio-inapp-native p /* نص الرسالة */
.joryio-inapp-native img /* الصورة */
.joryio-inapp-native button.primary /* الزر الأول */
.joryio-inapp-native button.secondary /* البقية */
.joryio-inapp-close /* الإغلاق */
.joryio-inapp-backdrop /* طبقة التعتيم */
.joryio-inapp-modal / -banner / -slideup / -fullscreen /* حسب النوع */

فضِّل المتغيرات على المحدِّدات. فكل ما يمكن للحملة ضبطه يُقرأ من متغير CSS، وضبطه على :root يمنحك أسلوبًا موحّدًا يظل بإمكان الحملة تجاوزه لرسالة بعينها:

:root {
--joryio-inapp-bg: #0A1240;
--joryio-inapp-fg: #FFFFFF;
--joryio-inapp-primary: #00C8B7;
--joryio-inapp-primary-fg: #041028;
--joryio-inapp-radius: 18px;
--joryio-inapp-font: 'Inter', system-ui, sans-serif;
--joryio-inapp-size: 15px;
--joryio-inapp-align: start; /* start | center | end */
--joryio-inapp-title-weight: 700;
}

ترتيب الأولوية من الأعلى:

  1. ما تضبطه الحملة في Style (optional)، ويُكتب inline على البطاقة
  2. قيمك أنت في --joryio-inapp-*
  3. الإعدادات الافتراضية للحزمة، وهي ألوان النظام

فالحملة التي لا تضبط شيئًا ترث أسلوبك، والتي تضبط خلفية تفوز لتلك الرسالة وحدها. دون !important في أي موضع.

وإن استخدمت المحدِّدات بدلًا من ذلك، فورقة أنماط الحزمة تُحقن وقت العرض، أي أنها تأتي بعد أنماطك في ترتيب المستند فتفوز عند التعادل. زد التخصيص - .joryio-inapp .joryio-inapp-native {…} - بدل قاعدة بفئة واحدة.

والاتجاه مضبوط تلقائيًا: تحمل البطاقة dir="auto"، فتُحاذى الرسالة العربية أو العبرية إلى اليمين داخل صفحة تسير من اليسار إلى اليمين.

أنواع الرسائل​

  1. Modal - وسط الشاشة مع خلفية معتمة
  2. Banner - أعلى الصفحة
  3. Slide-Up - إشعار صغير من الأسفل
  4. Full-Screen - رسالة تملأ الشاشة
  5. Custom - صفحتك تحدد الموضع

خيارات العرض​

const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_YOUR_KEY',
inApp: {
displayOptions: {
defaultPosition: 'bottom', // Custom-type messages only
defaultAnimation: 'slide', // 'fade' (default), 'slide' or 'none'
backdropColor: 'rgba(10, 18, 64, 0.6)',
closeButtonColor: '#ffffff',
zIndex: 2147483000,
},
},
});

Callbacks​

تتلقى كل دالة الرسالة؛ وحملتها هي message.campaign (message.campaign.id وmessage.campaign.name).

const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_YOUR_KEY',
inApp: {
onMessageDisplay: (message) => console.log('shown', message.campaign.id),
onMessageClick: (message, action) => console.log('clicked', message.campaign.id, action), // action: the button id
onMessageDismiss: (message, action) => console.log('dismissed', message.campaign.id, action), // e.g. 'close_button'
},
});

هذه الدوال إشعارات فقط: يواصل SDK رسم الرسالة بنفسه.

الـ callback الذي يرمي خطأ يُسجَّل ويُتجاهل: يواصل SDK تسجيل الظهور أو النقرة أو الإغلاق، ويُنظّف بعده.

عندما يعمل مشغّلان معاً (الحدث نفسه مرتين مثلاً)، تُعرض الرسالة - وتُحسب - مرة واحدة.

التحكم اليدوي​

await joryio.syncInAppCampaigns(); // fetch campaigns now
const campaigns = joryio.getInAppCampaigns(); // what is currently eligible
await joryio.displayInAppCampaign(campaignId); // show one now
joryio.dismissInAppMessage(campaignId); // close it
await joryio.trackInAppConversion(campaignId, eventName); // record a conversion
الدالةالوصف
syncInAppCampaigns(attributeChanges?)يطلب الآن من الخادم حملات هذا الزائر. لا يفعل شيئاً ما دام التتبع متوقفاً أو الموافقة معلقة
getInAppCampaigns()الحملات المؤهلة حالياً كما تمت مزامنتها
displayInAppCampaign(campaignId)عرض إحدى هذه الحملات الآن (يظل حد التكرار سارياً)
dismissInAppMessage(campaignId)إغلاق رسالة معروضة (تُسجَّل كـ dismissed)
trackInAppConversion(campaignId, eventName)تسجيل تحويل لحملة

متى تُحتسب الرسالة معروضة​

تُحتسب الرسالة معروضة - أي يُرسَل in_app.displayed ويُستهلك حد التكرار للحملة - فقط عندما يكون الشخص قد تمكّن فعلًا من رؤيتها. المدة التي يجب أن تبقى فيها على الشاشة هي IN_APP_DISPLAY_DWELL_MS (1000 مللي ثانية)، وهو الثابت نفسه في كل SDK من Joryio.

  • لا تظهر الرسالة إلا بعد أن تنتهي الصفحة من التحميل وتهدأ، ولا تظهر أبدًا أثناء مغادرة الصفحة (إعادة توجيه، الانتقال عبر رابط، إغلاق علامة التبويب).
  • تُحتسب معروضة بعد بقائها على الشاشة ثانية واحدة وعلامة التبويب ظاهرة. إذا أُخفيت علامة التبويب قبل ذلك، تبدأ الثانية من جديد عند ظهورها.
  • إذا غادرت الصفحة قبل ذلك - إعادة توجيه بعد تسجيل الدخول، رابط تسجيل الخروج - لا يُرسَل شيء ولا يُستهلك الحد، وتُعرض الرسالة مجددًا في الصفحة التالية.
  • النقر أو الإغلاق قبل ذلك يُحتسب مشاهدة: يُرسَل displayed أولًا ثم النقر أو الإغلاق. إزالة الرسالة من الكود (dismissInAppMessage، reset()) قبل ذلك لا تُرسل شيئًا.
  • يُستدعى onDisplay عندما تُحتسب الرسالة معروضة، مع الإرسال نفسه - لا عند وضعها على الشاشة. تصدّر الحزمة IN_APP_DISPLAY_DWELL_MS.

يقرر مفتاحان مستقلان ما إذا كان يُجمع أي شيء: الموافقة (إجابة شريط ملفات تعريف الارتباط لديك) وإيقاف التتبع الذي يختاره الزائر نفسه. لا يجمع SDK إلا عندما تكون الموافقة ممنوحة ولم يوقف الزائر التتبع - وأي "لا" هي الغالبة.

joryio.updateConsent('granted'); // or 'denied' or 'pending'
joryio.optOut(); // remembered across page loads until optIn()
joryio.optIn();

joryio.ready(function (sdk) {
sdk.getTrackingConsent(); // 'granted' | 'denied' | 'pending'
sdk.isUserOptedOut(); // the opt-out switch only
});
الحالةما يفعله SDK
الموافقة granted (أو غير محددة، دون رفض محفوظ)يجمع ويرسل كالمعتاد
الموافقة pendingيجمع محلياً ولا يرسل شيئاً - لا Session Start ولا مزامنة رسائل داخل التطبيق ولا تسجيل إشعارات - حتى يطلق updateConsent('granted') كل ذلك. ويحذفه updateConsent('denied'). ما يُحتفظ به يُحفظ في localStorage فلا تفقده إعادة التحميل: الأحداث وidentify() (حتى maxQueueSize حدثاً)، وكتابات السمات (حتى 500)، وتغييرات الأسماء المستعارة والاشتراكات (حتى 200). ويُستثنى إلغاء الاشتراك: فهو يُرسل على أي حال. ولا يبلّغ Service Worker الخاص بإشعارات الويب في الأثناء عن التسليم أو النقرات
الموافقة deniedلا يجمع شيئاً ويمسح كل ما في الطابور. ويتوقف Service Worker عن الإبلاغ عن التسليم والنقرات
optOut()لا يجمع شيئاً، ويمسح الأحداث في الطابور وكتابات السمات والأسماء المستعارة والاشتراك المعلقة (أما إلغاءات الاشتراك فتُرسل مع ذلك)، ويُحفظ فيبقى بعد إعادة التحميل. يواصل Service Worker عرض الإشعارات لكنه يتوقف عن الإبلاغ عن التسليم والنقرات
Do Not Trackيُعامل كإيقاف للتتبع ما دام respectDoNotTrack بقيمة true (الافتراضي)

كيف يتصرف المفتاحان:

  • denied يُحفظ. يبقى سارياً في مشاهدات الصفحة التالية حتى updateConsent('granted') أو trackingConsent: 'granted' صريح في إعدادات init. ترك trackingConsent دون قيمة يُبقي الرفض المحفوظ - ولهذا ليس للخيار قيمة افتراضية.
  • pending لمشاهدة صفحة واحدة. لا يُحفظ أبداً، ولا يتغلب أبداً على رفض محفوظ.
  • الموافقة لا تلغي إيقاف التتبع أبداً. updateConsent('granted') لا يلغي optOut()؛ وحده optIn() يفعل. ولا يغير optOut() وoptIn() الموافقة المحفوظة أبداً.
  • الملف الشخصي يسجل إيقاف التتبع. عندما يتغير مفتاح الإيقاف - بما في ذلك optOut: true عند init - يكتب SDK السمة tracking_opted_out (true أو false) في الملف الشخصي، مع معرّفات الزائر الذي اتخذ القرار، ويعيد المحاولة وفق قاعدة إعادة المحاولة. لا يُرسل شيء ما دام المفتاح لم يتغير. تُرسل true حتى والموافقة denied أو pending؛ أما false فتنتظر حتى يُسمح بالتتبع.
  • السحب يُسلَّم دائماً. إلغاء الاشتراك (SubscriptionStatus.UNSUBSCRIBED وremoveFromSubscriptionGroup()) وعلامة tracking_opted_out وإلغاء تسجيل الإشعارات تُرسل حتى والزائر قد أوقف التتبع، أو الموافقة denied أو pending، وبعد wipeData(). وكل كتابة أخرى تتوقف معها.
  • الجلسة التي بدأت والتتبع متوقف تحصل على Session Start الخاص بها مرة واحدة عند منح الموافقة أو عند عودة الزائر إلى التتبع.
  • identify() والتتبع متوقف (الموافقة مرفوضة، أو ألغى الزائر التتبع) لا يرسل شيئاً ولا يكتب شيئاً في أي ملف شخصي. يُحفظ معرّف المستخدم على هذا الجهاز فقط، كي يصل إلغاء اشتراك أو إلغاء تتبع لاحق إلى ذلك الشخص لا إلى ملف شخصي مجهول؛ ويعمل identify() العادي حين يُسمح بالتتبع من جديد. ويُنسيه reset() وwipeData(). عند منح الموافقة أو إعادة تفعيل التتبع، يُنفَّذ identify() المحفوظ قبل Session Start لأي جلسة بدأت في الأثناء، فتكون تلك الجلسة للمستخدم الجديد. ويُستخدم المعرّف المحفوظ في كل انسحاب: إلغاء الاشتراك، وإلغاء الاشتراك في مجموعة، والعلامة tracking_opted_out، وpush.unsubscribe(). ويحل محله أي identify() بعد السماح بالتتبع، ويتقدّم عليه userId صريح في إعدادات التهيئة.
  • كل علامات التبويب المفتوحة تتبع التغيير. إلغاء التتبع أو تغيير الموافقة أو reset() أو identify() في علامة تبويب واحدة من موقعك يسري فوراً على علامات التبويب المفتوحة الأخرى، لا عند إعادة تحميلها فقط. تسجيل الخروج في علامة تبويب واحدة هو reset() كامل في الأخرى: تنتقل إلى الزائر الجديد والجلسة الجديدة، وتبقى الكتابات التي أجرتها للمستخدم المغادر له. ومنح الموافقة في علامة تبويب يحرر أيضاً علامة تبويب تنتظر في pending. ولا يرفع استدعاء localStorage.clear() من علامة تبويب أو سكربت آخر إلغاءَ التتبع أو الرفض أبداً - تحتفظ به علامة التبويب وتحفظه من جديد. وتُدمج كتابات الملف الشخصي ورموز المستخدمين الذين سجّلوا خروجهم التي تحفظها كل علامة تبويب في localStorage، ولا تكتب فوقها أي علامة تبويب. ويُتبع أيضاً المستخدم الذي يحتفظ به identify() للانسحابات والتتبع متوقف. وupdateConsent('granted') أو optIn() صريح يُستدعى مباشرة بعد localStorage.clear() كهذا أحدث من القرار المحفوظ ويغلب في كل علامات التبويب. ولا ترسل علامة التبويب هذه أبداً كتابة ملف شخصي سلّمتها علامة تبويب أخرى بالفعل أو استبدلتها بكتابة أحدث، وwipeData() في علامة تبويب واحدة يتجاهل أيضاً الكتابات المنتظرة في علامات التبويب الأخرى (باستثناء الانسحابات).
  • مع الشيفرة غير المتزامنة، تسري هذه الاستدعاءات قبل أن يرسل init أي شيء - راجع الشيفرة غير المتزامنة.

مسح البيانات المحلية​

await joryio.wipeData();

يمسح كل ما يحتفظ به SDK عن الزائر في هذا المتصفح: الهوية (معرّف مجهول جديد، دون معرّف مستخدم)، والأحداث في الطابور وidentify() المعلق، وكتابات السمات والأسماء المستعارة والاشتراكات المعلقة، وحالة الرسائل داخل التطبيق وسجل مرات الظهور، والجلسة، وبيانات UTM، ومعرّف الجهاز، واشتراك الإشعارات (يُلغى تسجيله لدى Joryio أيضاً، ويبقى ملغى حتى يُستدعى push.subscribe() من جديد). وعمليات السحب التي ما زالت منتظرة - إلغاء اشتراك، أو علامة إيقاف التتبع، أو إلغاء تسجيل الإشعارات - تُسلَّم مع ذلك، بالمعرّفات التي أُجريت بها. وهو يُبقي على إيقاف التتبع وعلى رفض الموافقة، ولا يوقف التتبع لأحد - فذلك عمل optOut(). ويستمر التتبع كزائر مجهول جديد، في جلسة جديدة لها Session Start خاص بها.

مرجع API​

التهيئة​

const joryio = new JoryioSDK(config)

هيئ SDK بإعداداتك. يعيد المنشئ singleton؛ فإذا أنشأته مرة ثانية يعيد المثيل القائم، وهو ما تعيده أيضاً JoryioSDK.getInstance() (أو null قبل التهيئة).

لا يرمي SDK أي خطأ إلى صفحتك أبداً. يُسجَّل sdkKey المفقود أو غير الصالح مرة واحدة أياً كان مستوى التسجيل، ويتجاهل ذلك المثيل كل الاستدعاءات؛ والوسيط null حيث تلزم قيمة يُسجَّل ويُتجاهل.

Identify​

joryio.identify(userId)

اربط معرّف مستخدم بالجلسة الحالية.

المعاملات:

  • userId، string: المعرّف الفريد للمستخدم.

مثال:

joryio.identify('user_123');
joryio.setAttributes({
name: 'John Doe',
plan: 'premium',
});

Track​

joryio.track(eventName, properties?)

تتبّع حدثاً مخصصاً مع خصائص اختيارية.

المعاملات:

  • eventName، string: اسم الحدث.
  • properties، object اختياري: خصائص الحدث.

مثال:

joryio.track('Plan Upgraded', {
from_plan: 'basic',
to_plan: 'premium',
});

للمشتريات استخدم أداة التجارة الإلكترونية، فهي ترسل المفاتيح التي تقرؤها تقارير الإيرادات (orderId وtotal في حدث Order Completed):

joryio.ecommerce({ currency: 'USD' }).purchase({
orderId: 'order_789',
items: [{ productId: 'SKU123', name: 'Blue Shirt', price: 49.99, quantity: 3 }],
value: 149.99, // sent as `total`
});

راجع تتبع التجارة الإلكترونية لكل دوال الأداة. تتحقق كل دالة من وسائطها المطلوبة - productId أو orderId أو value منتهية للطلب أو مصفوفة items - وإن نقص أحدها تكتب تحذيراً في السجل ولا ترسل شيئاً (لا طلب فارغاً ولا طلب بقيمة صفر أبداً). ولا ترمي أبداً استثناءً في صفحتك. ويأخذ ecommerce() خياراً واحداً هو currency (الافتراضي 'USD').

تتبع مشاهدات الصفحة

فعّل تتبع مشاهدات الصفحة التلقائي:

const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_...',
trackPageViews: true
});

Add Alias​

joryio.addAlias(aliasLabel, aliasName)

أضف alias مُسمى لهذا الزائر. يعمل أيضاً قبل identify(): يحمل الطلب المعرّف المجهول (ومعرّف المستخدم بعد التعريف)، ويُربط الـ alias بالملف الشخصي الذي يشيران إليه.

لربط زائر مجهول بمعرّف المستخدم الخاص به، استدعِ identify(). عند أول تسجيل دخول يصبح الملف المجهول هو ملف المستخدم بكل تاريخه؛ وإذا كان للمستخدم ملف بالفعل، يُدمج الملف المجهول فيه. أُزيلت الدالة alias() غير المُسماة في الإصدار 1.3.0.

joryio.addAlias('crm', 'crm_98765');

Reset​

joryio.reset()

تسجيل خروج المستخدم الحالي. يحصل الزائر على معرّف مجهول جديد وجلسة جديدة، وتُمسح رسائل in-app الخاصة بالمستخدم السابق ويُنسى رمز مصادقة SDK الخاص به، وتُرسل كتابات السمات والأسماء المستعارة والاشتراكات الخاصة به التي لم تُؤكَّد بعد مع ذلك، بمعرّفاته هو - لا بمعرّفات الزائر التالي أبداً، وتُمسح بيانات UTM (بما فيها إسناد اللمسة الأولى والأخيرة)، وتُجلب حملات in-app للزائر الجديد، ويُعاد تسجيل اشتراك إشعارات الويب الموجود تحت المعرّف المجهول الجديد، فتتوقف إشعارات المستخدم السابق عن الوصول إلى هذا المتصفح.

تصبح الهوية الجديدة سارية بمجرد أن تعود reset(). والـ promise الذي تعيده لا يغطي إلا إرسال ما كان في القائمة من قبل (يحتفظ كل حدث منتظر بالمعرّفات التي سُجّل بها)، لذا فإن reset(); identify(nextUser) آمن دون await.

// On user logout
function handleLogout() {
joryio.reset();
// ... other logout logic
}

الحصول على Anonymous ID​

joryio.getAnonymousId()

يعيد معرّف المجهول الحالي، وهو المعرّف الذي يعيّنه Joryio لكل زائر قبل تعريفه. يولّد عند التهيئة الأولى ويحفظ في localStorage ويظل ثابتاً عبر تحميلات الصفحة طوال حياة الزائر المجهول؛ وتعيد reset() توليده عند تسجيل الخروج. وهو anonymousId الدقيق الملحق بكل حدث يرسله track()، فاستخدمه لربط حدث من الخادم أو سجل موافقة بالملف نفسه.

الإرجاع:

  • string - معرّف مجهول موجود دائماً.
اقرأه بعد تحميل SDK

مع المقتطف غير المتزامن، يكون window.joryio قائمة أوامر حتى ينتهي تحميل SDK، ولا يمكن لاستدعاء في الانتظار إعادة قيمة. اقرأ المعرّف من المثيل بعد التحميل أو ضمن ready() أدناه.

joryio.ready(function (sdk) {
const anonId = sdk.getAnonymousId();
// attach it to your own consent log / server event
fetch('/consent', { method: 'POST', body: JSON.stringify({ anonymousId: anonId }) });
});

الحصول على User ID​

joryio.getUserId()

يعيد معرّف المستخدم الحالي المعرّف، أو null إن كان الزائر مجهولاً وما زال identify() لم يُستدعَ.

الإرجاع:

  • string | null
joryio.ready(function (sdk) {
const userId = sdk.getUserId(); // null until you call joryio.identify(...)
});

Ready​

joryio.ready(callback)

شغّل callback(sdk) عند تحميل SDK وتهيئته. هذه هي الطريقة الآمنة لقراءة قيمة، مثل getAnonymousId() أو getUserId()، لا يستطيع مقتطف قائمة الأوامر إعادتها قبل التحميل. يتلقى callback مثيل SDK، ويعمل فوراً إذا كان محملاً أصلاً.

joryio.ready(function (sdk) {
console.log('anon:', sdk.getAnonymousId(), 'user:', sdk.getUserId());
});

الحصول على بيانات UTM​

joryio.getUTMData()

احصل على معاملات UTM الحالية ولمسة الأولى واللمسة الأخيرة.

الإرجاع: كائن يحوي بيانات UTM في current وfirstTouch وlastTouch.

const utmData = joryio.getUTMData();
console.log(utmData.current?.utm_source); // "google"
console.log(utmData.firstTouch?.utm_campaign); // "awareness_campaign"
console.log(utmData.lastTouch?.utm_campaign); // "conversion_campaign"

تحديث UTM​

joryio.updateUTM()

حدّث معاملات UTM يدوياً من URL الحالي. وهو مفيد لتطبيقات الصفحة الواحدة التي تغير URL بلا إعادة تحميل.

// React Router
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function App() {
const location = useLocation();

useEffect(() => {
joryio.updateUTM();
}, [location]);
}

// Vue Router
router.afterEach(() => {
joryio.updateUTM();
});

السمات​

joryio.setAttributes(attributes)
joryio.setAttribute(key, value)
joryio.incrementAttribute(key, by?)
joryio.unsetAttribute(key)
  • setAttributes({ plan: 'pro', city: 'Athens' }) يضبط عدة سمات معاً؛ وsetAttribute('plan', 'pro') يضبط سمة واحدة.
  • القيمة null أو undefined تحذف السمة، تماماً مثل unsetAttribute(key).
  • القيمة التي لا يستطيع JSON حملها - NaN أو Infinity أو BigInt أو دالة أو كائن يشير إلى نفسه - تُستبعد مع تحذير في السجل: يُترك ذلك المفتاح ويكتمل باقي الاستدعاء. وينطبق الأمر نفسه على خصائص الأحداث. لا تعطّل قيمة واحدة سيئة أي كتابات أخرى.
  • incrementAttribute('logins', 1) يضيف إلى رقم على الخادم (الافتراضي 1)، فلا تكتب علامتا تبويب فوق عدّ بعضهما أبداً.
  • الكتابات المتقاربة تُرسل في طلب واحد. وهي تنتظر أولاً وصول identify() المعلق إلى الخادم، وتُحفظ في localStorage حتى تُؤكَّد، والكتابة الفاشلة يعاد إرسالها وفق قاعدة إعادة المحاولة.
  • تحتفظ الكتابة بالهوية التي أُجريت تحتها: الكتابة التي تسبق identify() مباشرة تُرسل بمعرّف ذلك المستخدم بعد وصول identify إلى الخادم؛ وكتابة المستخدم A التي ما زالت منتظرة عند استدعاء identify('B') أو reset() تذهب إلى A مع ذلك.

سمات المصفوفة​

joryio.addToArray(key, value)
joryio.removeFromArray(key, value)

عدّل سمات المصفوفة بكفاءة.

المعاملات:

  • key، string: اسم السمة.
  • value، any: القيمة المطلوب إضافتها أو إزالتها.
// Add tags to user
joryio.addToArray('tags', 'vip');
joryio.addToArray('tags', 'premium');
// Result: tags = ['vip', 'premium']

// Add duplicate (no-op, prevents duplicates)
joryio.addToArray('tags', 'vip');
// Result: tags = ['vip', 'premium'] (unchanged)

// Remove tag
joryio.removeFromArray('tags', 'vip');
// Result: tags = ['premium']

// Common use cases
joryio.addToArray('interests', 'technology');
joryio.addToArray('purchasedProducts', 'prod_123');
joryio.addToArray('featureFlags', 'beta-access');

السلوك:

  • تضيف addToArray() القيمة فقط إن لم تكن موجودة، فتمنع التكرارات.
  • تنشئ addToArray() مصفوفة جديدة إن لم تكن السمة موجودة.
  • تزيل removeFromArray() كل مثيلات القيمة.
  • تتزامن التغييرات تلقائياً مع الخلفية للمستخدمين المعروفين والمجهولين.

تحافظ الاستدعاءات على السمة نفسها على ترتيبها: يصل استدعاءان لـ addToArray() على المفتاح نفسه كلاهما إلى الخادم، ويُرسل addToArray() يليه removeFromArray() للمفتاح نفسه واحداً تلو الآخر، لا في طلب واحد أبداً.

Flush​

await joryio.flush()

يرسل ما في القائمة الآن بدلاً من انتظار الدفعة التالية. وينتظر أيضاً إرسالاً جارياً بالفعل.

يتخطى flush() مهلة إعادة المحاولة الخاصة بـ SDK، لكنه لا يتخطى أبداً Retry-After طلبه API.

معلومات الهوية والجلسة​

joryio.getIdentity() // { userId, anonymousId, attributes }
joryio.getSessionInfo() // { sessionId, sessionStart, lastActivity, isNewSession }

يحتوي attributes على ما ضبطته هذه الصفحة (ولا يُقرأ من الخادم).

joryio.getTrackingConsent() // 'granted' | 'denied' | 'pending'
joryio.isUserOptedOut() // true after optOut(), until optIn()

يعكس isUserOptedOut() إيقاف الزائر نفسه للتتبع فقط - لا الموافقة ولا Do Not Track. راجع الخصوصية والموافقة. مع الشيفرة غير المتزامنة، اقرأهما داخل ready().

Destroy​

await joryio.destroy()

يرسل ما في القائمة، ويوقف كل المؤقتات، ويزيل مستمعي الصفحة، ويلغي تحميل الإضافات. وأي new JoryioSDK(config) لاحق يبدأ مثيلاً جديداً.

إشعارات الويب​

تحتاج إشعارات الويب إلى service worker على أصلك (origin) أنت (تشترط المتصفحات ذلك). استضف في جذر موقعك ملفاً اسمه joryio-sw.js يحتوي على سطر واحد:

importScripts('https://sdk.joryio.com/web/1/joryio-sw.js');

ثم اطلب الإذن من إجراء يقوم به المستخدم، مثل النقر على زر:

if (joryio.push.isSupported() && joryio.push.getPermissionState() === 'notDetermined') {
button.onclick = () => joryio.push.subscribe();
}
الدالةالوصف
push.isSupported()هل يدعم هذا المتصفح إشعارات الويب أصلاً (يلزم HTTPS وservice worker وPush API)
push.getPermissionState()'granted' أو 'denied' أو 'notDetermined' أو 'unavailable' - الأسماء نفسها في كل SDK من Joryio
push.prepare(options?)يسجل service worker ويجلب المفتاح مسبقاً، فيعرض subscribe() لاحقاً طلب الإذن فوراً. لا يطلب الإذن أبداً
push.subscribe(options?)يطلب الإذن (عند الحاجة) ويشترك ويسجل هذا المتصفح في Joryio. يعيد الاشتراك أو null
push.getExistingSubscription(options?)الاشتراك الحالي دون طلب إذن
push.unsubscribe(options?)يزيل اشتراك المتصفح ويلغي تسجيله في Joryio. ويبقى ملغى في الزيارات اللاحقة حتى تُستدعى subscribe() من جديد. لا يُرفض أبداً؛ ويعيد false إذا لم يكن هناك ما يُزال

يقبل options الخاصيتين swPath (الافتراضي '/joryio-sw.js') وscope (الافتراضي '/') إذا استضفت الـ worker في مكان آخر.

لا حاجة إلى استدعاء subscribe() مرة أخرى في الزيارات اللاحقة. عندما يكون الإذن ممنوحاً، يفحص SDK اشتراك المتصفح عند التحميل ويعيد تسجيله إذا بدّله المتصفح، أو إذا تغيّر مفتاح مساحة عملك (يعيد الاشتراك دون طلب إذن)، أو بعد reset() - ما لم تُستدعَ unsubscribe(). ويحمل كل تسجيل معرّف الجهاز الثابت الخاص بـ SDK. ولا يُسجَّل شيء ما دام التتبع متوقفاً أو الموافقة معلقة. أما إلغاء التسجيل الذي يجريه unsubscribe() فهو سحب: يُرسل حتى في هذه الحالات، ويُحفظ حتى يرد API، ويعاد إرساله وفق قاعدة إعادة المحاولة. وإعادة تسجيل المتصفح نفسه تلغي إلغاء تسجيل لم يُرسل بعد.

يعرض service worker كل إشعار بعنوانه ونصه وأيقونته وصورته وشارته (badge) وإجراءاته وtag (الإشعار الأحدث بالـ tag نفسه يحل محل الإشعار الظاهر) وrequireInteraction (يبقى حتى يُغلق)، ويبلغ عن تسليمه ويسجل النقرة. ويقرأ هذه المفاتيح من data الخاصة بالإشعار، وهي الأسماء نفسها التي يقرؤها SDK الخاص بـ Android وiOS:

المفتاحالأثر
trackingIdيُستخدم لإيصال التسليم ولتتبع النقر. يغيب في إرسالات الاختبار، التي لا تُحتسب
jry_deep_linkالصفحة التي يفتحها النقر (جذر الموقع إذا غاب)
jry_image_urlصورة كبيرة، في المتصفحات التي تعرضها
jry_api_originالوجهة التي يرسل إليها الـ worker إيصال التسليم والنقرة
jry_campaign_idمعرّف الحملة، أو معرّف الرحلة عند الإرسال من رحلة. للعلم فقط

مصادقة SDK​

اختيارية. عند تفعيلها يحمل كل طلب JWT يصدره خادمك في الترويسة X-Joryio-Auth، ويحدّثه SDK عندما يرفضه API. يثبت الرمز هوية الزائر المعروف: يواصل الزوار المجهولون العمل من دونه.

يوقّع خادمك الـ JWT بمفتاح تسجله في Joryio، مع sub يطابق معرّف المستخدم الذي تمرره إلى identify(). ويُجاب الرمز الخاطئ أو المنتهي أو المفقود بـ 401 { error: 'sdk_authentication_error', reason: 'missing' | 'invalid' | 'expired' | 'sub_mismatch' }.

const joryio = new JoryioSDK({
sdkKey: 'jry_sdk_web_YOUR_KEY',
enableSdkAuthentication: true,
sdkAuthenticationToken: await mintJoryioJwt(),
});

// Fires on a 401 sdk_authentication_error. Return a fresh token (or call
// setSdkAuthenticationToken); the failed request is then retried once.
joryio.setSdkAuthenticationErrorCallback(async (info) => {
// info = { reason, endpoint, userId }
return await mintJoryioJwt();
});

// Replace the token at any time, e.g. before it expires
joryio.setSdkAuthenticationToken(await mintJoryioJwt());
الدالةالوصف
setSdkAuthenticationToken(token)ضبط الـ JWT أو استبداله. يُتجاهل (مع تحذير) ما لم تكن قيمة enableSdkAuthentication هي true
setSdkAuthenticationErrorCallback(callback)تعمل callback(info) عند رفض الرمز؛ أعد رمزاً جديداً أو promise لرمز
  • يعمل الـ callback عند 401 لمصادقة SDK على طلب المستخدم الحالي ما دام enableSdkAuthentication مفعلاً - حتى قبل تعيين أي رمز (reason: 'missing'). ولا يعمل أبداً لطلب مستخدم سجّل خروجه منذ ذلك الحين، فلا يمكنه أن يجعل تطبيقك يسجّل خروج الشخص الذي يستخدمه الآن.
  • إعادة محاولة فورية واحدة. يُعاد إرسال الطلب الفاشل مرة واحدة بالرمز الجديد. إن لم يوجد callback، أو لم يُعد رمزاً، أو فشلت إعادة المحاولة أيضاً، فلا يُحذف شيء: تبقى الأحداث في الطابور ويُعاد إرسالها مع مهلة إعادة المحاولة العادية، بالرمز المعيّن حينها. ويعاد إرسال كتابة الملف الشخصي 10 مرات على الأكثر.
  • يُحتفظ برمز المستخدم الذي سجّل خروجه لما تركه. عند reset() - أو identify() لمستخدم آخر - يتوقف الرمز عن كونه رمز الجلسة لكنه يبقى مرتبطاً بأحداث المستخدم المغادر وكتاباته المنتظرة في الطابور، ويُحفظ معها في localStorage كي لا تُفقده إعادة التحميل، ويُحذف بمجرد تسليم آخرها أو حذفه. والرمز الذي تضبطه للمستخدم الداخل قبل استدعاء identify() يُتعرَّف عليه من sub الخاص به ويبقى له. تعيين رمز يعود sub فيه إلى مستخدم آخر يحتفظ جانباً بالطريقة نفسها بالرمز الذي يحل محله، فلا يهم الترتيب: يبقى ما تركه المستخدم المغادر يحمل رمزه. وتحديث رمز المستخدم نفسه لا يحتفظ بشيء جانباً. ويُحتفظ بالرمز المحفوظ جانباً دقيقة على الأقل و30 يوماً على الأكثر. ويُحتفظ بهذه الطريقة بالرمز الذي لا يكون sub فيه هو المستخدم الحالي لذلك المستخدم حتى عندما لا يكون أحد مسجلاً دخوله، إلى أن يجعله identify() رمز الجلسة؛ ويحذف wipeData() مثل هذا الرمز.
  • مستخدم واحد لكل طلب. لا تُجمع الأحداث أبداً في دفعة واحدة عبر تغيير المستخدم. وreset() يُخرج الرمز من الجلسة؛ ويبقى مع ما وضعه المستخدم المغادر في الطابور. أما الأحداث وعمليات الكتابة التي وضعها المستخدم المغادر في قائمة الانتظار فتُرسل مع رمزه هو، فلا يضيع شيء عند تسجيل الخروج العادي. أما أحداث مستخدم لم يعد الحالي وidentify() وكتابات الملف الشخصي الخاصة به التي رُفضت بأي 401 لمصادقة SDK (missing أو sub_mismatch أو expired أو invalid) فتُحذف - إذ لن يُعيَّن رمز لهذا المستخدم مجدداً - فلا تعطل أبداً ما يخص المستخدم التالي. لا يحمل الطلب إلا رمزاً يكون sub فيه هو مستخدم ذلك الطلب: الرمز الحالي عندما يكون له، وإلا الرمز المحفوظ جانباً له، وإلا لا شيء - فالرمز الذي تعيّنه للمستخدم التالي قبل identify() لا يخرج أبداً مع طلبات المستخدم الحالي. والطلبات المجهولة لا تحمل رمزاً.

تفضيلات الاشتراك​

يحدّث الكائن user، المخصص للكتابة فقط، اشتراكات التسويق الخاصة بالزائر. تنتظر التغييرات وصول identify() المعلق إلى الخادم، وتحمل معرّف المستخدم والمعرّف المجهول معاً، ويعاد إرسالها إذا لم تكن جهة الاتصال موجودة بعد. والتغيير الذي يسبق identify() يُرسل بمعرّف المستخدم بعد وصول identify إلى الخادم، فتقبله مصادقة SDK. وتُحفظ التغييرات في localStorage حتى تُؤكَّد وتُسلَّم بالترتيب: التغيير الأحدث على قناة أو مجموعة يحل محل تغيير أقدم ما زال منتظراً. ويُسلَّم إلغاء الاشتراك حتى والزائر قد أوقف التتبع أو الموافقة مرفوضة.

import { SubscriptionStatus } from '@joryio/web-sdk';

joryio.user.setSubscription('email', SubscriptionStatus.OPTED_IN);
joryio.user.setSubscriptions({
email: SubscriptionStatus.OPTED_IN,
sms: SubscriptionStatus.UNSUBSCRIBED,
});

joryio.user.addToSubscriptionGroup('newsletter-list-id', 'email');
joryio.user.removeFromSubscriptionGroup('newsletter-list-id', 'email');
الدالةالوصف
user.setSubscription(channel, status)قناة واحدة
user.setSubscriptions(preferences)عدة قنوات بالشكل { channel: status }
user.addToSubscriptionGroup(groupId, channel?)الانضمام إلى مجموعة اشتراك (قائمة)؛ القيمة الافتراضية لـ channel هي 'email'
user.removeFromSubscriptionGroup(groupId, channel?)مغادرة مجموعة اشتراك

القنوات: email وsms وwhatsapp وpush وviber. الحالات: SubscriptionStatus.OPTED_IN (موافقة صريحة، مثل double opt-in مؤكد) وSubscriptionStatus.SUBSCRIBED (موافقة بسيطة) وSubscriptionStatus.UNSUBSCRIBED. ومع نسخة CDN مرّر السلاسل 'optedIn' أو 'subscribed' أو 'unsubscribed'.

ما تصدّره الحزمة​

التصديرما هو
default / JoryioSDKصنف SDK (window.Joryio في نسخة CDN)
SubscriptionStatusحالات الاشتراك أعلاه
User وPushManager وEcommerceTrackerالأصناف خلف joryio.user وjoryio.push وjoryio.ecommerce() - للأنواع؛ لا تنشئها بنفسك
createEcommerceTracker(sdk, config?)مثل sdk.ecommerce(config)
SDK_VERSIONسلسلة إصدار SDK، مثل '1.3.3'
الأنواعInitConfig, InAppOptions, InAppDisplayOptions, InAppCampaign, InAppMessage, InAppTrigger, Plugin, TrackingConsent, PushPermissionState, PushSubscribeOptions, SubscriptionChannel, SubscriptionPreferences, UTMParameters, UTMData, EcommerceProduct, EcommerceCartItem, EcommerceOrder, EcommerceConfig, SdkAuthFailureInfo, SdkAuthFailureReason, SdkAuthFailureCallback

يُصدَّر installSnippetQueue لاختبارات SDK نفسه؛ لا تستدعه.

بيانات الجلسة التلقائية​

يثري Web SDK تلقائياً أحداث Session Start ببيانات الجهاز والبيئة. تحفظ هذه القيم في ملف المستخدم وسجلات الجهاز:

  • $user_agent
  • $timezone
  • $screen_width / $screen_height
  • $viewport_width / $viewport_height
  • $language / $languages
  • $platform
  • $browser
  • $device_id
  • country، رمز ISO-3166-1 alpha-2 مستخرج من IP عند بدء الجلسة.

خصائص الحدث​

الخصائص القياسية​

يحمل كل حدث مخزن هذه الخصائص: يلحق SDK $device_id، وتضيف قناة الاستقبال في Joryio الباقي عند تلقي الحدث:

  • $device_id: معرّف جهاز ثابت لكل متصفح.
  • $session_id: معرّف الجلسة الحالية.
  • $anonymous_id: معرّف المستخدم المجهول قبل التعريف.
  • $app_id: معرّف تطبيقك.
  • $app_name: اسم تطبيقك.
  • $platform: دائماً web لـ Web SDK.
  • $is_identified: ما إذا كان المستخدم معرّفاً.

البادئة $ محجوزة، فلا تستخدمها لخصائصك.

الخصائص المخصصة​

أضف أي خصائص مخصصة إلى أحداثك:

joryio.track('Video Played', {
video_id: 'vid_123',
video_title: 'Product Demo',
duration: 120,
autoplay: false,
// Any other custom data
});

أفضل الممارسات​

1. هيّئ مبكراً​

هيّئ SDK في أقرب وقت ممكن في تطبيقك:

// In your main app file
import JoryioSDK from '@joryio/web-sdk';

const joryio = new JoryioSDK({
sdkKey: process.env.JORYIO_SDK_KEY,
enableDebug: process.env.NODE_ENV === 'development',
});

2. تتبّع الأحداث المهمة​

ركز على الأحداث المهمة لنشاطك:

// Good: Specific, actionable events
joryio.track('Trial Started', { plan: 'premium' });
joryio.track('Feature Used', { feature: 'export', format: 'csv' });

// Avoid: Overly generic events
joryio.track('Button Clicked'); // Too generic

3. استخدم تسمية متسقة​

استخدم اصطلاحاً متسقاً للأحداث والخصائص:

// Good: Clear, consistent naming
joryio.track('Subscription Upgraded', {
from_plan: 'basic',
to_plan: 'premium',
billing_cycle: 'monthly',
});

// Avoid: Inconsistent naming
joryio.track('upgraded_subscription', {
FromPlan: 'basic',
'to-plan': 'premium',
});

4. تعامل مع دورة حياة المستخدم​

عالج تعريف المستخدم وإدارة الجلسة بصورة سليمة:

// On login
function handleLogin(userId, userInfo) {
joryio.identify(userId);
joryio.setAttributes({
email: userInfo.email,
name: userInfo.name,
});
}

// On logout
function handleLogout() {
joryio.reset();
}

// On signup
function handleSignup(userId, userInfo) {
joryio.identify(userId);
joryio.setAttributes(userInfo);
}

استكشاف الأخطاء وإصلاحها​

الأحداث لا تظهر​

  1. تحقق من مفتاح SDK وتأكد من أنه يبدأ بـ jry_sdk_web_ (يعمل أيضاً مفتاح تكامل المتجر). مفتاح تطبيق الجوال (jry_sdk_ios_ أو jry_sdk_android_) مرفوض: يسجّل SDK السبب ويبقى غير نشط.
  2. تحقق من وحدة التحكم وفعّل وضع التصحيح لرؤية سجلات مفصلة.
  3. تحقق من التهيئة وتأكد من تنفيذ new JoryioSDK(config) قبل أي دوال أخرى.

أخطاء CORS​

تستجيب نقاط تتبع SDK بـ Access-Control-Allow-Origin: *، لذلك لا تحتاج إلى قائمة سماح نطاقات. إذا استمر ظهور أخطاء CORS، تحقق من أن apiEndpoint يشير إلى عنوان الأساس الصحيح، https://api-eu1.joryio.com/api، ومن أن إضافة متصفح أو proxy لا يحجب الطلب أو يزيل رؤوس CORS.

مشاكل تتبع الجلسات​

يستخدم SDK localStorage لحفظ الجلسة. تأكد من:

  • تقديم موقعك عبر HTTPS، وهو مطلوب للسياقات الآمنة.
  • أن المستخدمين لم يعطلوا localStorage.
  • أنك لا تستدعي reset() من دون قصد.

الخطوات التالية​