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

محوّلات أطر العمل

حزمة TypeScript

حزمة واحدة بستة مداخل. استورد المدخل الذي يناسب بيئة التشغيل لديك، وسيترك بناء إزالة الشيفرة غير المستخدمة البقية خارج حزمتك.

مكان النشر

@prodantix/sdk on npm.

التثبيت

Bash
bun add @prodantix/sdk

مداخل الحزمة

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

SubpathWhat it exports
@prodantix/sdkProdantixClient, createClient, evaluateFlag, FlagsClient, MessagesClient
@prodantix/sdk/webcreateWebClient, installAutocapture, installLinkCarry, installSessionReplay, WebTransport
@prodantix/sdk/nodecreateNodeClient, readSharedIdentity
@prodantix/sdk/cspprodantixConnectSrc, prodantixImgSrc, prodantixScriptSrc
@prodantix/sdk/chatmountChat, ChatClient, ChatPanel, resolveAppearance
@prodantix/sdk/messengerfetchMessengerConfig, cachedMessengerConfig, MESSENGER_CACHE_KEY

@prodantix/sdk

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

TypeScript
import { createClient, MemoryStorage } from '@prodantix/sdk';

const prodantix = createClient({
  apiKey: PRODANTIX_PUBLIC_KEY,
  host: 'https://eu.api.prodantix.com',
  storage: new MemoryStorage(),
});

prodantix.capture('order.completed', { properties: { amount: 4200, currency: 'XAF' } });
await prodantix.flush();

@prodantix/sdk/web

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

TypeScript
import { createWebClient } from '@prodantix/sdk/web';

const prodantix = createWebClient({
  apiKey: PRODANTIX_PUBLIC_KEY,
  host: 'https://eu.api.prodantix.com',
  flagsHost: 'https://api.prodantix.com',
  cookieDomain: 'example.com',
  autocaptureOptions: { clicks: true, pageviews: true },
});

prodantix.identify('user-42', { set: { plan: 'pro' } });

if (await prodantix.isFeatureEnabled('new-checkout')) {
  // ...
}

// Every per-flag read records one $feature_flag_called exposure.
const variant = await prodantix.getVariant('new-checkout');
if (variant?.key === 'treatment') {
  // ...
}

// A flag targeting a cohort reads this user's memberships from the store;
// pass them yourself to skip the lookup.
if (await prodantix.isFeatureEnabledLocal('members', { cohorts: ['<cohort id>'] })) {
  // ...
}

على الويب يعيش المعرّف المجهول للزائر وجلسته في ملف تعريف ارتباط واحد من الطرف الأول على نطاقك القابل للتسجيل، فترى xyz.com وapp.xyz.com وconsole.xyz.com شخصًا واحدًا: زيارة التسويق والتسجيل وأول جلسة في التطبيق تقع على الخط الزمني نفسه بمجرد أن يعرّفه التطبيق. أما المعرّف المُعرَّف فيبقى لكل أصل في التخزين المحلي ولا يدخل ملف تعريف الارتباط أبدًا، لأن كل مضيف تحت النطاق يمكنه قراءته. يُعثر على النطاق بسؤال المتصفح، أو يُضبط عبر cookieDomain؛ والموقع الذي تعود نطاقاته الفرعية لمستأجرين مختلفين يجب أن يضبطه على مضيفه الخاص. مرّر sharedStorage لعدم كتابة أي ملف تعريف ارتباط، مثلًا حتى تُمنح الموافقة، ويبقى التخزين المحلي هو البديل عند رفض ملفات تعريف الارتباط. يُنشأ معرّف جلسة لكل زيارة، ويُبدَّل بعد ثلاثين دقيقة من الخمول أو أربع وعشرين ساعة، ويُختم على كل حدث ويُشارك مع إعادة تشغيل الجلسة.

لا يستطيع ملف تعريف الارتباط عبور نطاق قابل للتسجيل، فالزائر الذي يتبع رابطًا من موقع إلى آخر كان سيصبح زائرَين. مرّر linkedDomains، وهي فارغة افتراضيًا، فيزيّن العميل الروابط الخارجة إلى تلك النطاقات، عند click وauxclick، بمعامل _pdx يحمل ثلاثة أشياء: المعرّف المجهول، ومعرّف الجلسة، ولحظة بدء الجلسة، حتى تستمر نافذة الثلاثين دقيقة وسقف الأربع والعشرين ساعة عبر القفزة. لا ينتقل سوى المعرّف المجهول، ولا ينتقل معرّف مُعرَّف أبدًا، لأن الوجهة تعرّف الشخص بنفسها. أما الرابط إلى النطاق القابل للتسجيل نفسه، والرابط الذي يحمل _pdx أصلًا، وروابط mailto وtel وjavascript وروابط المرساة، فتُترك جميعها كما هي. وعند الوصول يقرأ createWebClient المعامل _pdx قبل بناء الهوية، ويكتب المعرّف والجلسة المحمولين في المخزن المشترك، ويزيل المعامل عبر history.replaceState فلا يظهر في $current_url ولا ينتقل إلى ما بعده؛ والمعامل الذي لا تُقرأ أجزاؤه الثلاثة يُتجاهل ويُترك العنوان كما هو. وإذا كان هذا المضيف يحمل بالفعل معرّفًا مجهولًا مختلفًا أرسل العميل $create_alias، فينطوي ما سجّله سابقًا في المعرّف المحمول؛ والمضيف الذي عرّف شخصًا من قبل يحتفظ بذلك الشخص: يصبح المعرّف المحمول معرّفه المجهول، لا معرّفه المميّز أبدًا.

TypeScript
import { createWebClient } from '@prodantix/sdk/web';

const prodantix = createWebClient({
  apiKey: PRODANTIX_PUBLIC_KEY,
  host: 'https://eu.api.prodantix.com',
  linkedDomains: ['example.com', 'example-app.com'],
});

// A link to example-app.com now leaves as
// https://example-app.com/pricing?_pdx=<anonymousId>~<sessionId>~<startedAt>
// and the arriving page adopts it before it builds an identity of its own.

// The visitor's answer, from your own consent banner.
prodantix.setConsent({ identityLink: false });
prodantix.consent(); // { identityLink: false }

أما جواب الزائر نفسه عن ذلك الضم فيركب المخزن المشترك ذاته، تحت pdx..consent، فيرى كل نطاق فرعي وكل خادم يقرأ ملف تعريف الارتباط جوابًا واحدًا. سجّله بـ setConsent({ identityLink: false })، واقرأه ثانية بـ consent()، وازرعه عبر خيار consent حين يكون تطبيقك يحمل الجواب أصلًا: فلافتة الموافقة من صنعك، وprodantix يحترم ما تجمعه. وما دام identityLink يساوي false، لا يرسل identify أي معرّف مجهول ليُضم إلى الشخص، ويُترك الرابط إلى نطاق مرتبط بلا تزيين، لأن نقل المعرّف عبره هو الضم نفسه بطريق آخر. وغير المضبوط أو true يسمح بالاثنين، رهنًا بإعداد المشروع نفسه في وحدة التحكم، وهو إعداد يفرضه عامل الحالة لا أي شيء يرسله المتصفح. ويسري تغيير الجواب على identify التالي وعلى النقرة التالية؛ ولا يُفكّ شيء سبق ضمه.

@prodantix/sdk/node

العميل نفسه بإعدادات خادم افتراضية. تُخزَّن الأحداث مؤقتًا وتُرسل في الخلفية، فاستدعِ shutdown قبل خروج العملية وإلا فلن تغادر الدفعة الأخيرة أبدًا.

TypeScript
import { createNodeClient } from '@prodantix/sdk/node';

// A node client streams flag changes over Socket.IO by default and refetches
// the snapshot on push; streamFlags: false leaves only the 30s poll.
const prodantix = createNodeClient({
  apiKey: process.env.PRODANTIX_PUBLIC_KEY,
  host: 'https://eu.api.prodantix.com',
  streamFlags: true,
});

prodantix.capture('job.completed', { properties: { queue: 'emails' } });
await prodantix.shutdown();

تسجيل الدخول الذي ينتهي بإعادة توجيه من الخادم لا يعرض أي صفحة، فلا يعمل identify الخاص بالمتصفح هناك أبدًا. تقرأ readSharedIdentity(cookieHeader, apiKey) ملف تعريف الارتباط الذي كتبه عميل الويب وتجيب بالمعرّف المجهول والجلسة التي هو فيها وموافقة الزائر، أو null حين يغيب الملف أو لا يكون ملف هذه المكتبة. ثم ترسل link(distinctId, anonymousId, traits) الحدث $identify الوحيد الذي كان المتصفح سيرسله، بالمعرّفَين المعطيين، وتترك هوية هذا العميل نفسه كما هي. ويستدعي تسجيل الدخول في Bomdisoft الاثنين عند اكتمال الدخول، داخل الدالة الوحيدة التي تستدعيها مواضع الدخول الثمانية أصلًا: فهناك يصبح subject الشخص معروفًا على الخادم أول مرة بينما ملف تعريف الارتباط ما زال على الطلب. وستة من تلك المواضع هي إجراءات خادم بلا كائن طلب، ولهذا تأخذ الدالة سلسلة ترويسة ملفات تعريف الارتباط لا الطلب. وتخطَّ link حين يكون الجواب المقروء identityLink يساوي false.

TypeScript
import { createNodeClient, readSharedIdentity } from '@prodantix/sdk/node';

const prodantix = createNodeClient({
  apiKey: PRODANTIX_PUBLIC_KEY,
  host: 'https://eu.api.prodantix.com',
});

// At sign-in completion, where the person is known and the browser's
// cookie is still on the request.
export const completeSignIn = (sub: string, cookieHeader: string): void => {
  const shared = readSharedIdentity(cookieHeader, PRODANTIX_PUBLIC_KEY);
  if (shared !== null && shared.consent?.identityLink !== false) {
    prodantix.link(sub, shared.anonymousId);
  }
};

@prodantix/sdk/csp

مصادر سياسة أمان المحتوى التي يحتاجها تطبيق للوصول إلى Prodantix. لا يستورد شيئًا من React ولا من المتصفح، فيستطيع ملف إعداد البناء قراءته مباشرة.

TypeScript
import { prodantixConnectSrc, prodantixImgSrc, prodantixScriptSrc } from '@prodantix/sdk/csp';

const hosts = {
  ingest: 'https://eu.api.prodantix.com',
  edge: 'https://eu.edge.prodantix.com',
  replay: 'https://eu.replay.prodantix.com',
};

const policy = [
  `connect-src ${prodantixConnectSrc(hosts)}`,
  `script-src ${prodantixScriptSrc(hosts)}`,
  `img-src ${prodantixImgSrc(hosts, ["'self'", 'data:'])}`,
].join('; ');

تعرض أداة الدعم صور فريقك من الـ edge وترسم الملفات التي يرسلها الزوار من عناوين blob:، لذلك يحتاج img-src في السياسة إلى الاثنين. تضيف prodantixImgSrc هذين المصدرين إلى مصادر الصور التي تسمح بها أصلًا. ومن دونهما يحجب المتصفح تلك الصور ولا يذكر ذلك إلا في وحدة التحكم الخاصة به.

@prodantix/sdk/chat

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

TypeScript
import { mountChat } from '@prodantix/sdk/chat';

const messenger = mountChat({
  apiKey: PRODANTIX_PUBLIC_KEY,
  host: 'https://eu.edge.prodantix.com',
  distinctId: prodantix.distinctId,
});

document.querySelector('#help')?.addEventListener('click', () => messenger.open());

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

@prodantix/sdk/messenger

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

TypeScript
import { fetchMessengerConfig } from '@prodantix/sdk/messenger';

const config = await fetchMessengerConfig({
  apiKey: PRODANTIX_PUBLIC_KEY,
  host: 'https://eu.edge.prodantix.com',
});

if (config?.enabled) {
  // the project has the messenger switched on
}