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.
- package/README.md +64 -5
- package/lib/commonjs/config.js +25 -0
- package/lib/commonjs/config.js.map +1 -1
- package/lib/commonjs/decisions.js +20 -6
- package/lib/commonjs/decisions.js.map +1 -1
- package/lib/commonjs/experiences.js +72 -0
- package/lib/commonjs/experiences.js.map +1 -0
- package/lib/commonjs/index.js +24 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/init.js +63 -17
- package/lib/commonjs/init.js.map +1 -1
- package/lib/commonjs/links.js +7 -1
- package/lib/commonjs/links.js.map +1 -1
- package/lib/commonjs/subject.js +44 -0
- package/lib/commonjs/subject.js.map +1 -0
- package/lib/module/config.js +24 -0
- package/lib/module/config.js.map +1 -1
- package/lib/module/decisions.js +20 -6
- package/lib/module/decisions.js.map +1 -1
- package/lib/module/experiences.js +66 -0
- package/lib/module/experiences.js.map +1 -0
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/init.js +64 -18
- package/lib/module/init.js.map +1 -1
- package/lib/module/links.js +7 -1
- package/lib/module/links.js.map +1 -1
- package/lib/module/subject.js +37 -0
- package/lib/module/subject.js.map +1 -0
- package/lib/typescript/config.d.ts +2 -0
- package/lib/typescript/config.d.ts.map +1 -1
- package/lib/typescript/decisions.d.ts +12 -0
- package/lib/typescript/decisions.d.ts.map +1 -1
- package/lib/typescript/experiences.d.ts +28 -0
- package/lib/typescript/experiences.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/init.d.ts +11 -0
- package/lib/typescript/init.d.ts.map +1 -1
- package/lib/typescript/links.d.ts +7 -16
- package/lib/typescript/links.d.ts.map +1 -1
- package/lib/typescript/subject.d.ts +12 -0
- package/lib/typescript/subject.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/config.ts +19 -0
- package/src/decisions.ts +36 -3
- package/src/experiences.ts +72 -0
- package/src/index.ts +2 -0
- package/src/init.ts +63 -19
- package/src/links.ts +14 -19
- 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"}
|
package/lib/typescript/init.d.ts
CHANGED
|
@@ -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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAEL,WAAW,EAGX,KAAK,aAAa,EAClB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,aAAa,EACb,uBAAuB,
|
|
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
|
-
*
|
|
181
|
+
* Where to read the signed-in address, when the integrator wants a first open to name it.
|
|
190
182
|
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
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
|
-
|
|
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
|
+
"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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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 =
|
|
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 =
|
|
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;
|