Перейти к основному содержимому

Клиентские библиотеки

Адаптеры фреймворков

TypeScript SDK

Один пакет с шестью точками входа. Импортируйте ту, что соответствует вашей среде выполнения, и сборка с встряхиванием дерева оставит остальное вне вашего бандла.

Где опубликован

@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>'] })) {
  // ...
}

В вебе анонимный id посетителя и сессия живут в одном first-party cookie на вашем регистрируемом домене, поэтому xyz.com, app.xyz.com и console.xyz.com видят одного человека: маркетинговый визит, регистрация и первая сессия в приложении ложатся на одну временную линию, как только приложение его идентифицирует. Идентифицированный id остаётся в локальном хранилище для каждого origin и никогда не попадает в cookie, потому что его может прочитать любой хост под этим доменом. Домен определяется запросом к браузеру или задаётся через cookieDomain; сайт, чьи поддомены принадлежат разным арендаторам, должен задать в нём собственный хост. Передайте sharedStorage, чтобы не записывать cookie вовсе, например до получения согласия; при отказе от cookie запасным вариантом остаётся локальное хранилище. Id сессии создаётся на каждый визит, сменяется после тридцати минут бездействия или двадцати четырёх часов, ставится на каждое событие и разделяется с повтором сессии.

Cookie не может пересечь регистрируемый домен, поэтому посетитель, перешедший по ссылке с одного сайта на другой, оказался бы двумя посетителями. Передайте linkedDomains, по умолчанию пустой, и клиент будет украшать исходящие ссылки на эти домены, при click и auxclick, параметром _pdx, который несёт три вещи: анонимный id, id сессии и момент начала сессии, чтобы окно в тридцать минут и предел в двадцать четыре часа продолжались после перехода. Путешествует только анонимный id, идентифицированный не переносится никогда, потому что человека опознаёт сам пункт назначения. Ссылка на тот же регистрируемый домен, ссылка, уже несущая _pdx, а также ссылки mailto, tel, javascript и якорные остаются нетронутыми. По прибытии createWebClient читает _pdx до того, как будет построена личность, записывает принесённый id и сессию в общее хранилище и снимает параметр через history.replaceState, так что он не появляется в $current_url и не едет дальше; параметр, три части которого не читаются, игнорируется, а адрес остаётся прежним. Если этот хост уже держал другой анонимный id, клиент отправляет $create_alias, и записанное ранее складывается в принесённый id; а хост, уже опознавший человека, этого человека сохраняет: принесённый id становится его анонимным id, но никогда не становится его distinct id.

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, так что каждый поддомен и сервер, читающий cookie, видят один ответ. Запишите его через setConsent({ identityLink: false }), прочитайте обратно через consent(), а если ваше приложение уже держит ответ, посейте его опцией consent: баннер согласия строите вы, а prodantix уважает то, что он собрал. Пока identityLink равен false, identify не отправляет анонимный id для объединения с человеком, а ссылка на перечисленный домен остаётся неукрашенной, ведь перенести id туда значит то же самое объединение другим путём. Не заданное или 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) читает cookie, записанный веб-клиентом, и отвечает анонимным id, сессией, в которой он находится, и согласием посетителя, либо null, когда cookie отсутствует или принадлежит не этому SDK. Затем link(distinctId, anonymousId, traits) отправляет тот единственный $identify, который отправил бы браузер, для переданных id, и не трогает собственную личность этого клиента. Вход Bomdisoft вызывает оба при завершении входа, внутри единственной функции, которую все восемь его точек входа уже вызывают: именно там subject человека впервые становится известен на сервере, пока cookie ещё находится в запросе. Шесть из этих точек являются серверными действиями без объекта запроса, поэтому функция принимает строку заголовка cookie, а не запрос. Пропустите 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 это только путь: никогда не запрос и никогда не фрагмент, потому что ссылка сброса, магическая ссылка или возврат из оплаты несут свой токен именно там, а входящие агента не место для него. Панель не рисует ничего из этого, поэтому ветка посетителя остаётся его собственными словами, а ответ края обратно в SDK тоже это опускает, так что страница, читающая свою ветку, не видит того, что видит консоль. Показывается это во входящих, приглушённой строкой под входящим сообщением.

@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
}