pulse-updates 1.3.10 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +64 -5
  2. package/lib/commonjs/config.js +25 -0
  3. package/lib/commonjs/config.js.map +1 -1
  4. package/lib/commonjs/decisions.js +20 -6
  5. package/lib/commonjs/decisions.js.map +1 -1
  6. package/lib/commonjs/experiences.js +72 -0
  7. package/lib/commonjs/experiences.js.map +1 -0
  8. package/lib/commonjs/index.js +24 -0
  9. package/lib/commonjs/index.js.map +1 -1
  10. package/lib/commonjs/init.js +63 -17
  11. package/lib/commonjs/init.js.map +1 -1
  12. package/lib/commonjs/links.js +7 -1
  13. package/lib/commonjs/links.js.map +1 -1
  14. package/lib/commonjs/subject.js +44 -0
  15. package/lib/commonjs/subject.js.map +1 -0
  16. package/lib/module/config.js +24 -0
  17. package/lib/module/config.js.map +1 -1
  18. package/lib/module/decisions.js +20 -6
  19. package/lib/module/decisions.js.map +1 -1
  20. package/lib/module/experiences.js +66 -0
  21. package/lib/module/experiences.js.map +1 -0
  22. package/lib/module/index.js +2 -0
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/init.js +64 -18
  25. package/lib/module/init.js.map +1 -1
  26. package/lib/module/links.js +7 -1
  27. package/lib/module/links.js.map +1 -1
  28. package/lib/module/subject.js +37 -0
  29. package/lib/module/subject.js.map +1 -0
  30. package/lib/typescript/config.d.ts +2 -0
  31. package/lib/typescript/config.d.ts.map +1 -1
  32. package/lib/typescript/decisions.d.ts +12 -0
  33. package/lib/typescript/decisions.d.ts.map +1 -1
  34. package/lib/typescript/experiences.d.ts +28 -0
  35. package/lib/typescript/experiences.d.ts.map +1 -0
  36. package/lib/typescript/index.d.ts +2 -0
  37. package/lib/typescript/index.d.ts.map +1 -1
  38. package/lib/typescript/init.d.ts +11 -0
  39. package/lib/typescript/init.d.ts.map +1 -1
  40. package/lib/typescript/links.d.ts +7 -16
  41. package/lib/typescript/links.d.ts.map +1 -1
  42. package/lib/typescript/subject.d.ts +12 -0
  43. package/lib/typescript/subject.d.ts.map +1 -0
  44. package/package.json +1 -1
  45. package/src/config.ts +19 -0
  46. package/src/decisions.ts +36 -3
  47. package/src/experiences.ts +72 -0
  48. package/src/index.ts +2 -0
  49. package/src/init.ts +63 -19
  50. package/src/links.ts +14 -19
  51. package/src/subject.ts +47 -0
@@ -0,0 +1,28 @@
1
+ import { type DecisionAction, type DecisionContext, type DecisionHandler, type PulseDecisionClient } from './decisions';
2
+ import { type ConfigStorage } from './config';
3
+ export interface PulseExperienceAction {
4
+ /** Exact capability, e.g. ad_pressure_profile:v1 or game_economy:v2. */
5
+ capability: string;
6
+ validate: (action: DecisionAction) => boolean;
7
+ execute: DecisionHandler;
8
+ /** Revoke persistent effects when the remote gate closes, identity changes, or SDK disposes. */
9
+ reset?: () => void;
10
+ }
11
+ export interface PulseExperienceOptions {
12
+ executionEnabledKey: string;
13
+ actions: Record<string, PulseExperienceAction>;
14
+ }
15
+ export interface PulseExperienceClient {
16
+ client: PulseDecisionClient;
17
+ capabilities: string[];
18
+ reset: () => void;
19
+ dispose: () => Promise<void>;
20
+ }
21
+ export declare function createPulseExperiences(options: PulseExperienceOptions & {
22
+ apiUrl: string;
23
+ appSlug: string;
24
+ getContext: () => DecisionContext;
25
+ storage?: ConfigStorage;
26
+ onError?: (error: unknown) => void;
27
+ }): PulseExperienceClient;
28
+ //# sourceMappingURL=experiences.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"experiences.d.ts","sourceRoot":"","sources":["../../src/experiences.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACnJ,OAAO,EAAoC,KAAK,aAAa,EAAE,MAAM,UAAU,CAAC;AAEhF,MAAM,WAAW,qBAAqB;IACpC,wEAAwE;IACxE,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,OAAO,CAAC;IAC9C,OAAO,EAAE,eAAe,CAAC;IACzB,gGAAgG;IAChG,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,sBAAsB;IACrC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,sBAAsB,GAAG;IACvE,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,eAAe,CAAC;IAClC,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC,GAAG,qBAAqB,CAyCxB"}
@@ -6,6 +6,8 @@ export * from './config';
6
6
  export * from './track';
7
7
  export * from './init';
8
8
  export * from './decisions';
9
+ export * from './subject';
10
+ export * from './experiences';
9
11
  export * from './links';
10
12
  export * from './nativeAttribution';
11
13
  export { DeferredLinkPasteButton } from './DeferredLinkPasteButton';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC7E,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,YAAY,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC7E,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,QAAQ,CAAC;AACvB,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,YAAY,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC"}
@@ -24,6 +24,9 @@
24
24
  * installs — a targeting bug that looks like a rollout that "did not work".
25
25
  */
26
26
  import { fetchConfig, type ConfigStorage, type ConfigValue, type ConfigSignature, getConfigInfo, getConfigBoolean, getConfigNumber, getConfigString, getConfigJson, getExposureDeliveryInfo } from './config';
27
+ import { type PulseUserProfile } from './subject';
28
+ import { type PulseExperienceOptions } from './experiences';
29
+ import type { PulseDecisionClient } from './decisions';
27
30
  import { getTrackingInfo, type TrackedEventInput } from './track';
28
31
  export interface InitPulseOptions {
29
32
  /** Where Pulse lives, e.g. https://pulse.example.com — no path. */
@@ -54,6 +57,10 @@ export interface InitPulseOptions {
54
57
  getUserId?: () => string | undefined;
55
58
  /** Attributes a rule can target — a plan, a storefront. Keep them few and stable. */
56
59
  getUserAttributes?: () => Record<string, string> | undefined;
60
+ /** Typed account context. registrationDate maps to the dashboard's registeredAt field. */
61
+ getUserProfile?: () => PulseUserProfile | undefined;
62
+ /** Versioned application actions controlled through a remote execution gate. */
63
+ experiences?: PulseExperienceOptions;
57
64
  /** Initial analytics consent or a live consent reader. Defaults to granted for compatibility. */
58
65
  analyticsConsent?: boolean | (() => boolean);
59
66
  /** Privacy guardrails applied to every track() call. */
@@ -103,6 +110,10 @@ export interface PulseClient {
103
110
  flush: () => Promise<number>;
104
111
  setUser: (userId?: string, attributes?: Record<string, string>) => void;
105
112
  clearUser: () => void;
113
+ identify: (profile: PulseUserProfile) => void;
114
+ /** Call after external profile readers change; clears old config and revokes applied effects. */
115
+ refreshContext: () => Promise<boolean>;
116
+ decisions?: PulseDecisionClient;
106
117
  setAnalyticsConsent: (granted: boolean) => void;
107
118
  resetInstallId: () => string;
108
119
  health: () => PulseHealth;
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/init.ts"],"names":[],"mappings":"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"}
@@ -79,14 +79,6 @@ export interface AnonymousFirstOpenContext {
79
79
  /** JavaScript Date#getTimezoneOffset semantics, bounded to real-world UTC offsets. */
80
80
  timezoneOffsetMinutes?: number;
81
81
  isEmulator?: boolean;
82
- /**
83
- * The address the person is signed in with, when the host app has one. An install that arrives
84
- * with an account does not need to be guessed at: the resolver binds it to the message sent to
85
- * that address and skips probabilistic matching entirely. Omitted for signed-out first opens,
86
- * and never read from the clipboard or any other ambient source — the app passes what it
87
- * already knows, or nothing.
88
- */
89
- accountEmail?: string;
90
82
  }
91
83
  export type AnonymousFirstOpenResult = 'matched' | 'no_match' | 'ineligible' | 'deterministic_pending' | 'already_completed' | 'backoff' | 'retry_scheduled' | 'terminal_error';
92
84
  export type AnonymousFirstOpenTerminalStatus = 'FOUND' | 'NOT_FOUND' | 'FAILURE';
@@ -186,16 +178,15 @@ export interface DeferredLinkClientOptions<Action extends string = DeferredLinkA
186
178
  /** Phase-one public campaign exposures are 128-bit hex; account tokens use the wider grammar. */
187
179
  isPublicToken?: (token: string) => boolean;
188
180
  /**
189
- * Whether a first open may name the signed-in account.
181
+ * Where to read the signed-in address, when the integrator wants a first open to name it.
190
182
  *
191
- * Off by default, and deliberately a second switch rather than "the host passed one, so send
192
- * it": an address is the only piece of the first-open request that identifies a person, and an
193
- * integrator adopting Pulse must choose to send it rather than discover later that reading their
194
- * own auth state started forwarding addresses. With it on, an install that arrives signed in is
195
- * bound to the message sent to that address instead of being inferred from device shape and
196
- * timing — the only deterministic signal left where no store referrer survives.
183
+ * Provide it and an install that arrives already signed in is bound to the message sent to that
184
+ * address, instead of being inferred from device shape and timing the only deterministic
185
+ * signal left where no store referrer survives. Leave it out and nothing about the account is
186
+ * ever sent: absent is off, so an integrator cannot enable this by accident, and there is no
187
+ * second switch to keep in sync with it. Read at the moment of the request, never stored.
197
188
  */
198
- sendAccountEmailOnFirstOpen?: boolean;
189
+ getAccountEmail?: () => string | null | undefined;
199
190
  }
200
191
  export interface DeferredLinkClient {
201
192
  capture(token: string, basis: DeferredLinkCaptureBasis): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../src/links.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../src/links.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /** Scalar signals supplied by the host app. Never send contact details or credentials. */
2
+ export type PulseAttributeValue = string | number | boolean | Date | null | undefined;
3
+ export interface PulseUserProfile {
4
+ id: string;
5
+ /** Backend account creation time, never installation/update time. */
6
+ registrationDate?: string | Date | null;
7
+ attributes?: Record<string, PulseAttributeValue>;
8
+ }
9
+ /** Normalize the wire format shared by config and decisions. pulse.* belongs to the server. */
10
+ export declare function normalizePulseAttributes(attributes?: Record<string, PulseAttributeValue>): Record<string, string>;
11
+ export declare function pulseProfileAttributes(profile?: PulseUserProfile): Record<string, string>;
12
+ //# sourceMappingURL=subject.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subject.d.ts","sourceRoot":"","sources":["../../src/subject.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;AACtF,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAClD;AAED,+FAA+F;AAC/F,wBAAgB,wBAAwB,CACtC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAC/C,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAmBxB;AAED,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAazF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulse-updates",
3
- "version": "1.3.10",
3
+ "version": "1.5.0",
4
4
  "description": "Pulse app-experience SDK for React Native: updates, config, experiments, events, decisions and first-party links",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
package/src/config.ts CHANGED
@@ -245,6 +245,25 @@ export function setConfigDefaults(more: Record<string, ConfigValue>): void {
245
245
  defaults = { ...defaults, ...more };
246
246
  }
247
247
 
248
+ /** Invalidate account-scoped values and pending responses before an identity change refresh. */
249
+ export function invalidateConfigContext(): void {
250
+ configGeneration += 1;
251
+ inFlight = null;
252
+ values = {};
253
+ experiments = [];
254
+ pending = null;
255
+ etag = null;
256
+ fetchedAt = 0;
257
+ verifiedAt = 0;
258
+ source = 'defaults';
259
+ signatureStatus = 'not-configured';
260
+ signatureKeyId = null;
261
+ reportedExposure = null;
262
+ exposureSending = false;
263
+ if (options) writeCache(options.storage, { values: {}, experiments: [], etag: null, fetchedAt: 0 }, options.storageKey);
264
+ notify();
265
+ }
266
+
248
267
  /**
249
268
  * Fetch, and adopt the payload if it changed. Returns true when new values were
250
269
  * applied — false for "nothing changed" (304) and false for a failure, which are
package/src/decisions.ts CHANGED
@@ -10,6 +10,8 @@ export interface DecisionContext {
10
10
  language?: string;
11
11
  osVersion?: string;
12
12
  attributes?: Record<string, string>;
13
+ /** Versioned action kinds this exact app build has wired an executor for. */
14
+ capabilities?: string[];
13
15
  }
14
16
 
15
17
  export interface DecisionAction { kind: string; [key: string]: unknown }
@@ -23,6 +25,12 @@ export interface PulseDecision {
23
25
  reasonCode: string;
24
26
  policyKey?: string | null;
25
27
  policyVersion?: number | null;
28
+ /** Exact measured experiment to tag on downstream product events. */
29
+ experimentKey?: string | null;
30
+ /** ISO timestamp bounding the client's offline assignment cache. */
31
+ experimentAssignmentExpiresAt?: string | null;
32
+ /** Stable control/treatment arm when the policy is linked to a measured experiment. */
33
+ variant?: string | null;
26
34
  action?: DecisionAction | null;
27
35
  }
28
36
 
@@ -44,6 +52,10 @@ export interface PulseDecisionClientOptions {
44
52
  timeoutMs?: number;
45
53
  /** Safe default. User-facing execution needs an explicit per-app opt-in. */
46
54
  executionMode?: 'shadow-only' | 'allow-live';
55
+ /** Rechecked after every response; use a remote flag to revoke live execution. */
56
+ isExecutionAllowed?: () => boolean;
57
+ /** Local lifecycle revision. Never sent to the server. */
58
+ getContextRevision?: () => number;
47
59
  handlers?: Record<string, DecisionHandler>;
48
60
  /** Persists terminal outcomes across process death and offline periods. */
49
61
  storage?: ConfigStorage;
@@ -174,13 +186,18 @@ export function createPulseDecisionClient(options: PulseDecisionClientOptions):
174
186
 
175
187
  const decide = async (trigger: string, request: DecideOptions = {}): Promise<PulseDecision | null> => {
176
188
  try {
189
+ if (disposed) return null;
190
+ const revision = options.getContextRevision?.();
177
191
  const context = await options.getContext();
192
+ const contextSnapshot = JSON.stringify(context);
178
193
  const response = await timedFetch(fetcher, `${base}/pulse/decisions/${slug}`, {
179
194
  method: 'POST',
180
195
  headers: { 'Content-Type': 'application/json' },
181
196
  body: JSON.stringify({
182
197
  ...context,
183
- attributes: { ...(context.attributes ?? {}), ...(request.attributes ?? {}) },
198
+ // pulse.* facts come only from trusted server-side profiles. Dropping them
199
+ // here is defence in depth; the API independently strips client claims.
200
+ attributes: safeDecisionAttributes(context.attributes, request.attributes),
184
201
  trigger,
185
202
  channel: request.channel ?? 'in_app',
186
203
  idempotencyKey: request.idempotencyKey ?? randomKey(),
@@ -189,8 +206,10 @@ export function createPulseDecisionClient(options: PulseDecisionClientOptions):
189
206
  if (!response.ok) throw new Error(`Pulse decision HTTP ${response.status}`);
190
207
 
191
208
  const decision = parseDecision(await response.json());
209
+ const currentContext = await options.getContext();
210
+ if (disposed || revision !== options.getContextRevision?.() || contextSnapshot !== JSON.stringify(currentContext)) return null;
192
211
  if (!decision.execute) return decision;
193
- if (executionMode !== 'allow-live') {
212
+ if (decision.mode !== 'live' || executionMode !== 'allow-live' || options.isExecutionAllowed?.() === false) {
194
213
  void reportOutcome(decision.decisionId, 'failed', { reason: 'client_executor_not_armed' });
195
214
  return decision;
196
215
  }
@@ -235,9 +254,10 @@ export function createPulseDecisionClient(options: PulseDecisionClientOptions):
235
254
  }),
236
255
  dispose: async () => {
237
256
  clearInterval(timer);
238
- await flushOutcomes();
257
+ const finalFlush = flushOutcomes();
239
258
  disposed = true;
240
259
  persistOutcomes();
260
+ await finalFlush;
241
261
  },
242
262
  };
243
263
  }
@@ -285,10 +305,23 @@ function parseDecision(raw: unknown): PulseDecision {
285
305
  reasonCode: value.reasonCode,
286
306
  policyKey: typeof value.policyKey === 'string' ? value.policyKey : null,
287
307
  policyVersion: typeof value.policyVersion === 'number' ? value.policyVersion : null,
308
+ experimentKey: typeof value.experimentKey === 'string' ? value.experimentKey : null,
309
+ experimentAssignmentExpiresAt: typeof value.experimentAssignmentExpiresAt === 'string'
310
+ ? value.experimentAssignmentExpiresAt
311
+ : null,
312
+ variant: typeof value.variant === 'string' ? value.variant : null,
288
313
  action: (action as DecisionAction | null | undefined) ?? null,
289
314
  };
290
315
  }
291
316
 
317
+ function safeDecisionAttributes(
318
+ context?: Record<string, string>,
319
+ request?: Record<string, string>,
320
+ ): Record<string, string> {
321
+ return Object.fromEntries(Object.entries({ ...(context ?? {}), ...(request ?? {}) })
322
+ .filter(([key]) => !key.toLowerCase().startsWith('pulse.')));
323
+ }
324
+
292
325
  async function timedFetch(
293
326
  fetcher: typeof globalThis.fetch,
294
327
  url: string,
@@ -0,0 +1,72 @@
1
+ import { createPulseDecisionClient, type DecisionAction, type DecisionContext, type DecisionHandler, type PulseDecisionClient } from './decisions';
2
+ import { getConfigBoolean, onConfigChange, type ConfigStorage } from './config';
3
+
4
+ export interface PulseExperienceAction {
5
+ /** Exact capability, e.g. ad_pressure_profile:v1 or game_economy:v2. */
6
+ capability: string;
7
+ validate: (action: DecisionAction) => boolean;
8
+ execute: DecisionHandler;
9
+ /** Revoke persistent effects when the remote gate closes, identity changes, or SDK disposes. */
10
+ reset?: () => void;
11
+ }
12
+
13
+ export interface PulseExperienceOptions {
14
+ executionEnabledKey: string;
15
+ actions: Record<string, PulseExperienceAction>;
16
+ }
17
+
18
+ export interface PulseExperienceClient {
19
+ client: PulseDecisionClient;
20
+ capabilities: string[];
21
+ reset: () => void;
22
+ dispose: () => Promise<void>;
23
+ }
24
+
25
+ export function createPulseExperiences(options: PulseExperienceOptions & {
26
+ apiUrl: string;
27
+ appSlug: string;
28
+ getContext: () => DecisionContext;
29
+ storage?: ConfigStorage;
30
+ onError?: (error: unknown) => void;
31
+ }): PulseExperienceClient {
32
+ const actions = { ...options.actions };
33
+ const capabilities = Object.entries(actions).map(([kind, action]) => {
34
+ if (!action.capability.startsWith(`${kind}:v`) || !/^.+:v[1-9]\d*$/.test(action.capability))
35
+ throw new Error(`Pulse: capability must be versioned and match action kind ${kind}`);
36
+ return action.capability;
37
+ });
38
+ if (!options.executionEnabledKey.trim()) throw new Error('Pulse: an execution gate key is required');
39
+ const enabled = () => getConfigBoolean(options.executionEnabledKey);
40
+ let generation = 0;
41
+ let disposed = false;
42
+ const reset = () => {
43
+ generation += 1;
44
+ for (const action of Object.values(actions)) {
45
+ try { action.reset?.(); } catch (error) { options.onError?.(error); }
46
+ }
47
+ };
48
+ let wasEnabled = enabled();
49
+ const unsubscribe = onConfigChange(() => {
50
+ const next = enabled();
51
+ if (wasEnabled && !next) reset();
52
+ wasEnabled = next;
53
+ });
54
+ const client = createPulseDecisionClient({
55
+ apiUrl: options.apiUrl, appSlug: options.appSlug, storage: options.storage,
56
+ executionMode: 'allow-live', isExecutionAllowed: enabled, onError: options.onError,
57
+ getContextRevision: () => generation,
58
+ getContext: () => ({ ...options.getContext(), capabilities }),
59
+ handlers: Object.fromEntries(Object.entries(actions).map(([kind, handler]) => [kind, async (action, decision) => {
60
+ if (action.requiredCapability !== handler.capability || !handler.validate(action))
61
+ return { outcome: 'failed' as const, metadata: { reason: 'invalid_action_contract' } };
62
+ return handler.execute(action, decision);
63
+ }])),
64
+ });
65
+ return { client, capabilities, reset, dispose: async () => {
66
+ if (disposed) return;
67
+ disposed = true;
68
+ unsubscribe();
69
+ reset();
70
+ await client.dispose();
71
+ } };
72
+ }
package/src/index.ts CHANGED
@@ -6,6 +6,8 @@ export * from './config';
6
6
  export * from './track';
7
7
  export * from './init';
8
8
  export * from './decisions';
9
+ export * from './subject';
10
+ export * from './experiences';
9
11
  export * from './links';
10
12
  export * from './nativeAttribution';
11
13
  export { DeferredLinkPasteButton } from './DeferredLinkPasteButton';
package/src/init.ts CHANGED
@@ -38,7 +38,11 @@ import {
38
38
  getConfigString,
39
39
  getConfigJson,
40
40
  getExposureDeliveryInfo,
41
+ invalidateConfigContext,
41
42
  } from './config';
43
+ import { normalizePulseAttributes, pulseProfileAttributes, type PulseUserProfile } from './subject';
44
+ import { createPulseExperiences, type PulseExperienceOptions } from './experiences';
45
+ import type { PulseDecisionClient } from './decisions';
42
46
  import {
43
47
  configureTracking,
44
48
  disposeTracking,
@@ -86,6 +90,10 @@ export interface InitPulseOptions {
86
90
 
87
91
  /** Attributes a rule can target — a plan, a storefront. Keep them few and stable. */
88
92
  getUserAttributes?: () => Record<string, string> | undefined;
93
+ /** Typed account context. registrationDate maps to the dashboard's registeredAt field. */
94
+ getUserProfile?: () => PulseUserProfile | undefined;
95
+ /** Versioned application actions controlled through a remote execution gate. */
96
+ experiences?: PulseExperienceOptions;
89
97
 
90
98
  /** Initial analytics consent or a live consent reader. Defaults to granted for compatibility. */
91
99
  analyticsConsent?: boolean | (() => boolean);
@@ -150,6 +158,10 @@ export interface PulseClient {
150
158
  flush: () => Promise<number>;
151
159
  setUser: (userId?: string, attributes?: Record<string, string>) => void;
152
160
  clearUser: () => void;
161
+ identify: (profile: PulseUserProfile) => void;
162
+ /** Call after external profile readers change; clears old config and revokes applied effects. */
163
+ refreshContext: () => Promise<boolean>;
164
+ decisions?: PulseDecisionClient;
153
165
  setAnalyticsConsent: (granted: boolean) => void;
154
166
  resetInstallId: () => string;
155
167
  health: () => PulseHealth;
@@ -170,6 +182,7 @@ export interface AsyncPulseStorage {
170
182
 
171
183
  let activeClient: PulseClient | null = null;
172
184
  let stopActiveRefresh: (() => void) | null = null;
185
+ let stopActiveExperiences: (() => Promise<void>) | null = null;
173
186
 
174
187
  /** Wires config and events with an explicit event-envelope identity policy. */
175
188
  export function initPulse(opts: InitPulseOptions): PulseClient {
@@ -178,6 +191,8 @@ export function initPulse(opts: InitPulseOptions): PulseClient {
178
191
  // fast app switch or test setup triggers. Persist the old outbox, stop its refresh,
179
192
  // then configure the new app in one uninterrupted turn.
180
193
  stopActiveRefresh?.();
194
+ void stopActiveExperiences?.();
195
+ stopActiveExperiences = null;
181
196
  disposeTracking();
182
197
  activeClient = null;
183
198
  stopActiveRefresh = null;
@@ -194,6 +209,7 @@ export function initPulse(opts: InitPulseOptions): PulseClient {
194
209
  const language = opts.language ?? detectLanguage();
195
210
  let userId: string | undefined;
196
211
  let userAttributes: Record<string, string> | undefined;
212
+ let userOverridden = false;
197
213
  let consentOverride: boolean | undefined = typeof opts.analyticsConsent === 'boolean'
198
214
  ? opts.analyticsConsent
199
215
  : undefined;
@@ -201,15 +217,18 @@ export function initPulse(opts: InitPulseOptions): PulseClient {
201
217
  const hasConsent = (): boolean => consentOverride ??
202
218
  (typeof opts.analyticsConsent === 'function' ? opts.analyticsConsent() : true);
203
219
 
204
- const getContext = (): ConfigContext => ({
205
- platform,
206
- osVersion,
207
- appVersion,
208
- language,
209
- deviceId,
210
- userId: userId ?? opts.getUserId?.(),
211
- userAttributes: userAttributes ?? opts.getUserAttributes?.(),
212
- });
220
+ const getContext = (): ConfigContext => {
221
+ const profile = userOverridden ? undefined : opts.getUserProfile?.();
222
+ return {
223
+ platform,
224
+ osVersion,
225
+ appVersion,
226
+ language,
227
+ deviceId,
228
+ userId: userOverridden ? userId : profile?.id ?? opts.getUserId?.(),
229
+ userAttributes: userOverridden ? userAttributes : profile ? pulseProfileAttributes(profile) : normalizePulseAttributes(opts.getUserAttributes?.()),
230
+ };
231
+ };
213
232
 
214
233
  configureConfig({
215
234
  url: configUrl,
@@ -262,6 +281,18 @@ export function initPulse(opts: InitPulseOptions): PulseClient {
262
281
  // off position of every flag.
263
282
  void fetchConfig();
264
283
  const stopRefresh = startConfigAutoRefresh(opts.appState);
284
+ const experiences = opts.experiences ? createPulseExperiences({
285
+ ...opts.experiences, apiUrl: base, appSlug: slug, storage: opts.storage, onError: opts.onError,
286
+ getContext: () => {
287
+ const { userAttributes: attributes, ...context } = getContext();
288
+ return { ...context, attributes };
289
+ },
290
+ }) : undefined;
291
+ const refreshContext = (): Promise<boolean> => {
292
+ experiences?.reset();
293
+ invalidateConfigContext();
294
+ return fetchConfig();
295
+ };
265
296
 
266
297
  const client: PulseClient = {
267
298
  get deviceId() { return deviceId; },
@@ -271,17 +302,34 @@ export function initPulse(opts: InitPulseOptions): PulseClient {
271
302
  trackIdempotent,
272
303
  flush: flushEvents,
273
304
  setUser: (nextUserId, attributes) => {
305
+ if (activeClient !== client) return;
306
+ userOverridden = true;
274
307
  userId = nextUserId?.trim() || undefined;
275
- userAttributes = sanitizeAttributes(attributes);
308
+ userAttributes = normalizePulseAttributes(attributes);
309
+ void refreshContext();
276
310
  },
277
311
  clearUser: () => {
312
+ if (activeClient !== client) return;
313
+ userOverridden = true;
278
314
  userId = undefined;
279
- userAttributes = undefined;
315
+ userAttributes = {};
316
+ void refreshContext();
317
+ },
318
+ identify: (profile) => {
319
+ if (activeClient !== client) return;
320
+ userOverridden = true;
321
+ userId = profile.id.trim() || undefined;
322
+ userAttributes = userId ? pulseProfileAttributes(profile) : {};
323
+ void refreshContext();
280
324
  },
325
+ refreshContext: () => activeClient === client ? refreshContext() : Promise.resolve(false),
326
+ decisions: experiences?.client,
281
327
  setAnalyticsConsent: (granted) => { consentOverride = granted; },
282
328
  resetInstallId: () => {
329
+ if (activeClient !== client) return deviceId;
283
330
  deviceId = randomId();
284
331
  try { opts.storage?.set(deviceStorageKey, deviceId); } catch { /* memory-only fallback */ }
332
+ void refreshContext();
285
333
  return deviceId;
286
334
  },
287
335
  health: () => ({
@@ -301,6 +349,7 @@ export function initPulse(opts: InitPulseOptions): PulseClient {
301
349
  },
302
350
  dispose: async () => {
303
351
  stopRefresh();
352
+ await experiences?.dispose();
304
353
  if (activeClient !== client) return;
305
354
  await flushEvents();
306
355
  // Another init may have taken ownership while this network flush was pending.
@@ -308,11 +357,13 @@ export function initPulse(opts: InitPulseOptions): PulseClient {
308
357
  disposeTracking();
309
358
  activeClient = null;
310
359
  stopActiveRefresh = null;
360
+ stopActiveExperiences = null;
311
361
  }
312
362
  },
313
363
  };
314
364
  activeClient = client;
315
365
  stopActiveRefresh = stopRefresh;
366
+ stopActiveExperiences = experiences?.dispose ?? null;
316
367
  return client;
317
368
  }
318
369
 
@@ -331,6 +382,7 @@ export async function initPulseAsync(
331
382
  `pulse.${slug}.events.v1`,
332
383
  `pulse.${slug}.events.v1.analytics-installation-id`,
333
384
  `pulse.${slug}.exposures.v1`,
385
+ `pulse.${slug}.outcomes.v1`,
334
386
  DEVICE_ID_KEY,
335
387
  ];
336
388
  const loaded = await Promise.all(keys.map(async (key) => [key, await opts.storage.getItem(key)] as const));
@@ -373,14 +425,6 @@ function resolveDeviceId(storage?: ConfigStorage, key = DEVICE_ID_KEY): string {
373
425
  }
374
426
  }
375
427
 
376
- function sanitizeAttributes(attributes?: Record<string, string>): Record<string, string> | undefined {
377
- if (!attributes) return undefined;
378
- const clean = Object.fromEntries(Object.entries(attributes)
379
- .filter(([key, value]) => key.trim() !== '' && typeof value === 'string')
380
- .slice(0, 50));
381
- return Object.keys(clean).length > 0 ? clean : undefined;
382
- }
383
-
384
428
  function randomId(): string {
385
429
  const bytes = new Uint8Array(16);
386
430
  const crypto = (globalThis as { crypto?: { getRandomValues?: (a: Uint8Array) => void } }).crypto;