본문으로 건너뛰기

클라이언트 라이브러리

프레임워크 어댑터

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을 건드리는 코드가 없으므로, 저장소와 전송, 시계, id 팩토리를 직접 넘겨 어디서든 실행할 수 있습니다.

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와 세션이 등록 가능한 도메인 위의 퍼스트 파티 쿠키 하나에 보관되므로 xyz.com, app.xyz.com, console.xyz.com이 같은 한 사람을 봅니다. 마케팅 방문, 가입, 앱에서의 첫 세션은 앱이 그 사람을 식별하는 순간 같은 타임라인에 놓입니다. 식별된 id는 오리진별로 로컬 스토리지에 남고 쿠키에는 절대 들어가지 않습니다. 도메인 아래의 모든 호스트가 쿠키를 읽을 수 있기 때문입니다. 도메인은 브라우저에 물어 찾거나 cookieDomain으로 지정합니다. 서브도메인이 서로 다른 테넌트에 속한 사이트는 자기 호스트로 지정해야 합니다. 쿠키를 아예 쓰지 않으려면, 예를 들어 동의를 받기 전까지는, sharedStorage를 전달하세요. 쿠키가 거부되면 로컬 스토리지가 계속 대체 저장소가 됩니다. 세션 id는 방문마다 발급되고, 삼십 분 유휴 또는 스물네 시간이 지나면 교체되며, 모든 이벤트에 찍히고 세션 리플레이와 공유됩니다.

쿠키는 등록 가능 도메인을 넘지 못하므로, 한 사이트에서 다른 사이트로 링크를 따라간 방문자는 두 명의 방문자가 되어 버립니다. 기본값이 비어 있는 linkedDomains를 넘기면 클라이언트가 click과 auxclick 시점에 그 도메인으로 나가는 링크에 _pdx 매개변수를 붙입니다. 이 매개변수는 세 가지를 실어 나릅니다. 익명 id, 세션 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 아래에 실립니다. 그래서 모든 서브도메인과 쿠키를 읽는 서버가 하나의 답을 봅니다. 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)는 웹 클라이언트가 쓴 쿠키를 읽어 익명 id, 그것이 속한 세션, 방문자의 동의를 돌려주며, 쿠키가 없거나 이 SDK의 것이 아니면 null을 돌려줍니다. 이어서 link(distinctId, anonymousId, traits)가 브라우저였다면 보냈을 그 한 건의 $identify를 주어진 id로 보내고, 이 클라이언트 자신의 신원은 건드리지 않습니다. Bomdisoft의 로그인은 로그인 완료 시점에 둘 다 호출합니다. 여덟 개의 로그인 지점이 이미 함께 호출하는 그 하나의 함수 안에서입니다. 그곳이 그 사람의 subject가 서버에서 처음 알려지고, 쿠키가 아직 요청에 실려 있는 자리입니다. 그 지점 가운데 여섯은 요청 객체가 없는 서버 액션이며, 그래서 이 함수는 요청이 아니라 쿠키 헤더 문자열을 받습니다. 다시 읽은 답이 identityLink false이면 link를 건너뛰십시오.

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
}