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
package/src/links.ts
CHANGED
|
@@ -122,14 +122,6 @@ export interface AnonymousFirstOpenContext {
|
|
|
122
122
|
/** JavaScript Date#getTimezoneOffset semantics, bounded to real-world UTC offsets. */
|
|
123
123
|
timezoneOffsetMinutes?: number;
|
|
124
124
|
isEmulator?: boolean;
|
|
125
|
-
/**
|
|
126
|
-
* The address the person is signed in with, when the host app has one. An install that arrives
|
|
127
|
-
* with an account does not need to be guessed at: the resolver binds it to the message sent to
|
|
128
|
-
* that address and skips probabilistic matching entirely. Omitted for signed-out first opens,
|
|
129
|
-
* and never read from the clipboard or any other ambient source — the app passes what it
|
|
130
|
-
* already knows, or nothing.
|
|
131
|
-
*/
|
|
132
|
-
accountEmail?: string;
|
|
133
125
|
}
|
|
134
126
|
|
|
135
127
|
export type AnonymousFirstOpenResult =
|
|
@@ -270,16 +262,15 @@ export interface DeferredLinkClientOptions<Action extends string = DeferredLinkA
|
|
|
270
262
|
/** Phase-one public campaign exposures are 128-bit hex; account tokens use the wider grammar. */
|
|
271
263
|
isPublicToken?: (token: string) => boolean;
|
|
272
264
|
/**
|
|
273
|
-
*
|
|
265
|
+
* Where to read the signed-in address, when the integrator wants a first open to name it.
|
|
274
266
|
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
* timing — the only deterministic signal left where no store referrer survives.
|
|
267
|
+
* Provide it and an install that arrives already signed in is bound to the message sent to that
|
|
268
|
+
* address, instead of being inferred from device shape and timing — the only deterministic
|
|
269
|
+
* signal left where no store referrer survives. Leave it out and nothing about the account is
|
|
270
|
+
* ever sent: absent is off, so an integrator cannot enable this by accident, and there is no
|
|
271
|
+
* second switch to keep in sync with it. Read at the moment of the request, never stored.
|
|
281
272
|
*/
|
|
282
|
-
|
|
273
|
+
getAccountEmail?: () => string | null | undefined;
|
|
283
274
|
}
|
|
284
275
|
|
|
285
276
|
export interface DeferredLinkClient {
|
|
@@ -1627,9 +1618,13 @@ export function createPulseLinkClient<Action extends string = DeferredLinkAction
|
|
|
1627
1618
|
return 'retry_scheduled';
|
|
1628
1619
|
}
|
|
1629
1620
|
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1621
|
+
let accountEmail: string | null = null;
|
|
1622
|
+
try {
|
|
1623
|
+
accountEmail = normalizeAccountEmail(options.getAccountEmail?.());
|
|
1624
|
+
} catch {
|
|
1625
|
+
// A host that cannot answer right now simply does not name the account; matching carries on.
|
|
1626
|
+
accountEmail = null;
|
|
1627
|
+
}
|
|
1633
1628
|
const body = {
|
|
1634
1629
|
appBundleId: context.appBundleId.trim(),
|
|
1635
1630
|
platform: 'ios' as const,
|
package/src/subject.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
|
|
10
|
+
/** Normalize the wire format shared by config and decisions. pulse.* belongs to the server. */
|
|
11
|
+
export function normalizePulseAttributes(
|
|
12
|
+
attributes?: Record<string, PulseAttributeValue>,
|
|
13
|
+
): Record<string, string> {
|
|
14
|
+
const result: Record<string, string> = {};
|
|
15
|
+
for (const [rawKey, value] of Object.entries(attributes ?? {})) {
|
|
16
|
+
const key = rawKey.trim();
|
|
17
|
+
if (!key || key.length > 128 || key.toLowerCase().startsWith('pulse.') || value == null) continue;
|
|
18
|
+
let scalar: string;
|
|
19
|
+
if (value instanceof Date) {
|
|
20
|
+
if (!Number.isFinite(value.getTime())) continue;
|
|
21
|
+
scalar = value.toISOString();
|
|
22
|
+
} else if (typeof value === 'number') {
|
|
23
|
+
if (!Number.isFinite(value)) continue;
|
|
24
|
+
scalar = String(value);
|
|
25
|
+
} else if (typeof value === 'string' || typeof value === 'boolean') scalar = String(value);
|
|
26
|
+
else continue;
|
|
27
|
+
if (scalar.length > 1024) continue;
|
|
28
|
+
result[key] = scalar;
|
|
29
|
+
if (Object.keys(result).length >= 64) break;
|
|
30
|
+
}
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function pulseProfileAttributes(profile?: PulseUserProfile): Record<string, string> {
|
|
35
|
+
const attributes = normalizePulseAttributes(profile?.attributes);
|
|
36
|
+
if (profile?.registrationDate != null) {
|
|
37
|
+
// An explicit invalid backend date must not fall back to a conflicting custom value.
|
|
38
|
+
for (const key of Object.keys(attributes)) if (key.toLowerCase() === 'registeredat') delete attributes[key];
|
|
39
|
+
const raw = profile.registrationDate;
|
|
40
|
+
const date = raw instanceof Date ? raw : /^\d{4}-\d{2}-\d{2}(T.*(Z|[+-]\d{2}:\d{2}))?$/i.test(raw) ? new Date(raw) : null;
|
|
41
|
+
if (date && Number.isFinite(date.getTime())) {
|
|
42
|
+
if (Object.keys(attributes).length >= 64) delete attributes[Object.keys(attributes)[63]!];
|
|
43
|
+
attributes.registeredAt = date.toISOString();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return attributes;
|
|
47
|
+
}
|