@getuserfeedback/protocol 0.7.2 → 0.9.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/dist/app-event.d.ts +147 -20
- package/dist/app-event.d.ts.map +1 -1
- package/dist/app-event.js +10 -0
- package/dist/flow-assignments.d.ts +7 -4
- package/dist/flow-assignments.d.ts.map +1 -1
- package/dist/flow-assignments.js +15 -5
- package/dist/host/external-id-argument-guards.d.ts +10 -0
- package/dist/host/external-id-argument-guards.d.ts.map +1 -0
- package/dist/host/external-id-argument-guards.js +27 -0
- package/dist/host/index.d.ts +1 -0
- package/dist/host/index.d.ts.map +1 -1
- package/dist/host/index.js +1 -0
- package/dist/host/sdk-types.d.ts +48 -6
- package/dist/host/sdk-types.d.ts.map +1 -1
- package/dist/identity-type.d.ts +2 -3
- package/dist/identity-type.d.ts.map +1 -1
- package/dist/identity-type.js +2 -4
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/version-resolution.d.ts +1 -0
- package/dist/version-resolution.d.ts.map +1 -1
- package/dist/version-resolution.js +1 -0
- package/dist/widget-commands.d.ts +23 -1
- package/dist/widget-commands.d.ts.map +1 -1
- package/dist/widget-commands.js +6 -1
- package/dist/widget-config.d.ts +18 -0
- package/dist/widget-config.d.ts.map +1 -1
- package/dist/widget-config.js +2 -1
- package/package.json +1 -1
- package/src/app-event.ts +24 -0
- package/src/flow-assignments.test.ts +19 -0
- package/src/flow-assignments.ts +31 -5
- package/src/host/external-id-argument-guards.ts +57 -0
- package/src/host/index.ts +1 -0
- package/src/host/sdk-types.ts +51 -7
- package/src/identity-type.ts +3 -9
- package/src/index.ts +4 -1
- package/src/protocol-root.test.ts +121 -3
- package/src/version-resolution.ts +1 -0
- package/src/widget-commands.ts +11 -1
- package/src/widget-config.ts +8 -1
package/src/host/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./command-dispatch.js";
|
|
|
6
6
|
export * from "./command-envelope.js";
|
|
7
7
|
export * from "./command-settlement.js";
|
|
8
8
|
export * from "./constants.js";
|
|
9
|
+
export * from "./external-id-argument-guards.js";
|
|
9
10
|
export * from "./host-event-contract.js";
|
|
10
11
|
export * from "./lazy-handle-client.js";
|
|
11
12
|
export { createCommandRequestId } from "./request-id.js";
|
package/src/host/sdk-types.ts
CHANGED
|
@@ -11,8 +11,8 @@ import type { Scope } from "../scopes.js";
|
|
|
11
11
|
/**
|
|
12
12
|
* Identifier types supported by event tracking and identity resolution.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* `
|
|
14
|
+
* Business-specific identifiers are sent as Segment-compatible
|
|
15
|
+
* `context.externalIds` entries on app events or identify options.
|
|
16
16
|
*
|
|
17
17
|
* @see https://getuserfeedback.com/docs/guides/identity-resolution
|
|
18
18
|
*/
|
|
@@ -23,15 +23,29 @@ export type AppEventIdentityType =
|
|
|
23
23
|
| "traits.phone"
|
|
24
24
|
| "context.device.id"
|
|
25
25
|
| "context.device.advertisingId"
|
|
26
|
-
| "context.device.token"
|
|
27
|
-
| `custom.${string}`;
|
|
26
|
+
| "context.device.token";
|
|
28
27
|
|
|
29
|
-
/** A single identifier, such as a user ID, anonymous ID, email, or
|
|
28
|
+
/** A single standard identifier, such as a user ID, anonymous ID, email, or device ID. */
|
|
30
29
|
export type AppEventIdentity = {
|
|
31
30
|
type: AppEventIdentityType;
|
|
32
31
|
value: string;
|
|
33
32
|
};
|
|
34
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Segment-compatible external identifier.
|
|
36
|
+
*
|
|
37
|
+
* Only `collection: "users"` participates in person identity resolution today.
|
|
38
|
+
* External IDs help match profiles, but they are not traits by themselves.
|
|
39
|
+
*
|
|
40
|
+
* @see https://getuserfeedback.com/docs/guides/identity-resolution
|
|
41
|
+
*/
|
|
42
|
+
export type AppEventExternalId = {
|
|
43
|
+
id: string;
|
|
44
|
+
type: string;
|
|
45
|
+
collection: "users";
|
|
46
|
+
encoding: "none";
|
|
47
|
+
};
|
|
48
|
+
|
|
35
49
|
/** Page, feature flag, and capability context attached to an event. */
|
|
36
50
|
export type AppEventContext = {
|
|
37
51
|
page?: {
|
|
@@ -41,6 +55,7 @@ export type AppEventContext = {
|
|
|
41
55
|
};
|
|
42
56
|
locale?: string;
|
|
43
57
|
userAgent?: string;
|
|
58
|
+
externalIds?: AppEventExternalId[] | undefined;
|
|
44
59
|
flags?: AppEventFlag[] | undefined;
|
|
45
60
|
capabilities?: AppEventCapability[] | undefined;
|
|
46
61
|
};
|
|
@@ -367,17 +382,19 @@ export type PrerenderCommandPayload = {
|
|
|
367
382
|
hideCloseButton?: boolean | undefined;
|
|
368
383
|
};
|
|
369
384
|
|
|
370
|
-
/** Identify with traits only (`client.identify(traits)`). */
|
|
385
|
+
/** Identify with traits only (`client.identify(traits)` or `client.identify(traits, options)`). */
|
|
371
386
|
export type IdentifyTraitsOnlyCommandPayload = {
|
|
372
387
|
kind: "identify";
|
|
373
388
|
traits: Record<string, unknown>;
|
|
389
|
+
options?: IdentifyOptions | undefined;
|
|
374
390
|
};
|
|
375
391
|
|
|
376
|
-
/** Identify with userId and optional
|
|
392
|
+
/** Identify with userId, optional traits, and optional external IDs (`client.identify(userId, traits?, options?)`). */
|
|
377
393
|
export type IdentifyUserCommandPayload = {
|
|
378
394
|
kind: "identify";
|
|
379
395
|
userId: string;
|
|
380
396
|
traits?: Record<string, unknown> | undefined;
|
|
397
|
+
options?: IdentifyOptions | undefined;
|
|
381
398
|
};
|
|
382
399
|
|
|
383
400
|
/** Identify command payload: either traits-only or userId+traits shape. */
|
|
@@ -385,6 +402,33 @@ export type IdentifyCommandPayload =
|
|
|
385
402
|
| IdentifyTraitsOnlyCommandPayload
|
|
386
403
|
| IdentifyUserCommandPayload;
|
|
387
404
|
|
|
405
|
+
/**
|
|
406
|
+
* Optional identity context accepted by `client.identify()`.
|
|
407
|
+
*
|
|
408
|
+
* Use `externalIds` for Segment-compatible identifiers from another system,
|
|
409
|
+
* such as Shopify customer IDs or Salesforce contact IDs. Without a user ID,
|
|
410
|
+
* pass options as `client.identify(traits, options)`, or use
|
|
411
|
+
* `client.identify(traits, undefined, options)` when your call site needs the
|
|
412
|
+
* explicit three-argument form.
|
|
413
|
+
*
|
|
414
|
+
* @see https://getuserfeedback.com/docs/guides/identity-resolution
|
|
415
|
+
*/
|
|
416
|
+
export type IdentifyOptions = {
|
|
417
|
+
externalIds?: AppEventExternalId[] | undefined;
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Optional context accepted by `client.track()`.
|
|
422
|
+
*
|
|
423
|
+
* Use `externalIds` when the event carries a Segment-compatible identifier
|
|
424
|
+
* from another system.
|
|
425
|
+
*
|
|
426
|
+
* @see https://getuserfeedback.com/docs/reference/events
|
|
427
|
+
*/
|
|
428
|
+
export type TrackOptions = {
|
|
429
|
+
externalIds?: AppEventExternalId[] | undefined;
|
|
430
|
+
};
|
|
431
|
+
|
|
388
432
|
/**
|
|
389
433
|
* Command payload for `client.track()`.
|
|
390
434
|
*
|
package/src/identity-type.ts
CHANGED
|
@@ -11,12 +11,7 @@ export const IDENTITY_TYPE_VALUES = [
|
|
|
11
11
|
] as const;
|
|
12
12
|
|
|
13
13
|
export type StandardIdentityTypeValue = (typeof IDENTITY_TYPE_VALUES)[number];
|
|
14
|
-
export type
|
|
15
|
-
export type IdentityTypeValue =
|
|
16
|
-
| StandardIdentityTypeValue
|
|
17
|
-
| CustomIdentityTypeValue;
|
|
18
|
-
|
|
19
|
-
const CUSTOM_IDENTITY_TYPE_PATTERN = /^custom\.[a-zA-Z0-9][a-zA-Z0-9_.-]*$/;
|
|
14
|
+
export type IdentityTypeValue = StandardIdentityTypeValue;
|
|
20
15
|
|
|
21
16
|
const IDENTITY_TYPE_ALIAS_SUGGESTIONS: Record<
|
|
22
17
|
string,
|
|
@@ -44,8 +39,7 @@ const normalizeIdentityTypeAliasKey = (value: string): string =>
|
|
|
44
39
|
export const isIdentityTypeValue = (
|
|
45
40
|
value: string,
|
|
46
41
|
): value is IdentityTypeValue =>
|
|
47
|
-
IDENTITY_TYPE_VALUES.some((candidate) => candidate === value)
|
|
48
|
-
CUSTOM_IDENTITY_TYPE_PATTERN.test(value);
|
|
42
|
+
IDENTITY_TYPE_VALUES.some((candidate) => candidate === value);
|
|
49
43
|
|
|
50
44
|
const parseIdentityTypeValue = (value: string): IdentityTypeValue | null =>
|
|
51
45
|
isIdentityTypeValue(value) ? value : null;
|
|
@@ -69,7 +63,7 @@ export const buildIdentityTypeGuidanceMessage = (value: string): string => {
|
|
|
69
63
|
([from, to]) => `${from} -> ${to}`,
|
|
70
64
|
).join(", ");
|
|
71
65
|
|
|
72
|
-
return `Unsupported identity type ${displayValue}. Use one of: ${IDENTITY_TYPE_VALUES.join(", ")}
|
|
66
|
+
return `Unsupported identity type ${displayValue}. Use one of: ${IDENTITY_TYPE_VALUES.join(", ")}. For external IDs, send Segment-compatible context.externalIds entries instead. Common mappings: ${mappingExamples}.`;
|
|
73
67
|
};
|
|
74
68
|
|
|
75
69
|
const identityTypeInputSchema = z.string().check(
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export {
|
|
2
2
|
type AppEventCapability,
|
|
3
|
+
type AppEventExternalId,
|
|
3
4
|
type AppEventFlag,
|
|
4
5
|
type AppEventJsonValue,
|
|
5
6
|
type AppEventPayload,
|
|
@@ -8,6 +9,7 @@ export {
|
|
|
8
9
|
appEventCapabilitySchema,
|
|
9
10
|
appEventContextSchema,
|
|
10
11
|
appEventCustomerTrackSchema,
|
|
12
|
+
appEventExternalIdSchema,
|
|
11
13
|
appEventFlagProviderSchema,
|
|
12
14
|
appEventFlagSchema,
|
|
13
15
|
appEventFlowDismissedSchema,
|
|
@@ -37,6 +39,8 @@ export {
|
|
|
37
39
|
type ClaimPendingFlowAssignmentsResponse,
|
|
38
40
|
claimPendingFlowAssignmentsRequestSchema,
|
|
39
41
|
claimPendingFlowAssignmentsResponseSchema,
|
|
42
|
+
type FlowAssignmentExternalIdIdentityTypeValue,
|
|
43
|
+
type FlowAssignmentIdentityTypeValue,
|
|
40
44
|
type FlowAssignmentRecipientRequest,
|
|
41
45
|
type FlowAssignmentRecord,
|
|
42
46
|
type FlowAssignmentStatus,
|
|
@@ -59,7 +63,6 @@ export {
|
|
|
59
63
|
} from "./flow-assignments.js";
|
|
60
64
|
export {
|
|
61
65
|
buildIdentityTypeGuidanceMessage,
|
|
62
|
-
type CustomIdentityTypeValue,
|
|
63
66
|
getIdentityTypeSuggestion,
|
|
64
67
|
IDENTITY_TYPE_VALUES,
|
|
65
68
|
type IdentityTypeValue,
|
|
@@ -161,6 +161,18 @@ describe("@getuserfeedback/protocol root contract", () => {
|
|
|
161
161
|
});
|
|
162
162
|
});
|
|
163
163
|
|
|
164
|
+
it("parses theme resolution payloads with optional runtime artifacts", () => {
|
|
165
|
+
expect(
|
|
166
|
+
themeVersionResolutionSchema.parse({
|
|
167
|
+
themeRuntimeArtifactId: "thra_xyz",
|
|
168
|
+
themeVersionId: "tv_xyz",
|
|
169
|
+
}),
|
|
170
|
+
).toEqual({
|
|
171
|
+
themeRuntimeArtifactId: "thra_xyz",
|
|
172
|
+
themeVersionId: "tv_xyz",
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
164
176
|
it("exposes public widget command kind validation aligned with public payloads", () => {
|
|
165
177
|
expect(publicWidgetCommandKindSchema.safeParse("open").success).toBe(true);
|
|
166
178
|
expect(publicWidgetCommandKindSchema.safeParse("telemetry").success).toBe(
|
|
@@ -196,6 +208,16 @@ describe("@getuserfeedback/protocol root contract", () => {
|
|
|
196
208
|
traits: {
|
|
197
209
|
email: "user@example.com",
|
|
198
210
|
},
|
|
211
|
+
options: {
|
|
212
|
+
externalIds: [
|
|
213
|
+
{
|
|
214
|
+
id: "gid://shopify/Customer/123",
|
|
215
|
+
type: "shopify_customer_id",
|
|
216
|
+
collection: "users",
|
|
217
|
+
encoding: "none",
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
},
|
|
199
221
|
});
|
|
200
222
|
const withTraitsOnly = parsePublicCommand({
|
|
201
223
|
kind: "identify",
|
|
@@ -210,6 +232,14 @@ describe("@getuserfeedback/protocol root contract", () => {
|
|
|
210
232
|
expect(withUserId.traits).toEqual({
|
|
211
233
|
email: "user@example.com",
|
|
212
234
|
});
|
|
235
|
+
expect(withUserId.options?.externalIds).toEqual([
|
|
236
|
+
{
|
|
237
|
+
id: "gid://shopify/Customer/123",
|
|
238
|
+
type: "shopify_customer_id",
|
|
239
|
+
collection: "users",
|
|
240
|
+
encoding: "none",
|
|
241
|
+
},
|
|
242
|
+
]);
|
|
213
243
|
}
|
|
214
244
|
|
|
215
245
|
expect(withTraitsOnly.kind).toBe("identify");
|
|
@@ -468,9 +498,97 @@ describe("@getuserfeedback/protocol root contract", () => {
|
|
|
468
498
|
);
|
|
469
499
|
});
|
|
470
500
|
|
|
471
|
-
it("
|
|
472
|
-
|
|
501
|
+
it("rejects legacy custom app event identity types", () => {
|
|
502
|
+
expect(() =>
|
|
503
|
+
appEventIdentityTypeSchema.parse("custom.shopifyCustomerId"),
|
|
504
|
+
).toThrow(/context\.externalIds/);
|
|
505
|
+
});
|
|
473
506
|
|
|
474
|
-
|
|
507
|
+
it("accepts Segment-compatible external IDs in app event context", () => {
|
|
508
|
+
const result = appEventTrackSchema.parse({
|
|
509
|
+
event: "Checkout Started",
|
|
510
|
+
origin: "customer",
|
|
511
|
+
context: {
|
|
512
|
+
externalIds: [
|
|
513
|
+
{
|
|
514
|
+
id: "gid://shopify/123",
|
|
515
|
+
type: "shopify_customer_id",
|
|
516
|
+
collection: "users",
|
|
517
|
+
encoding: "none",
|
|
518
|
+
},
|
|
519
|
+
],
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
expect(result.context?.externalIds).toEqual([
|
|
524
|
+
{
|
|
525
|
+
id: "gid://shopify/123",
|
|
526
|
+
type: "shopify_customer_id",
|
|
527
|
+
collection: "users",
|
|
528
|
+
encoding: "none",
|
|
529
|
+
},
|
|
530
|
+
]);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it("normalizes external ID values before app event context output", () => {
|
|
534
|
+
const result = appEventTrackSchema.parse({
|
|
535
|
+
event: "Checkout Started",
|
|
536
|
+
origin: "customer",
|
|
537
|
+
context: {
|
|
538
|
+
externalIds: [
|
|
539
|
+
{
|
|
540
|
+
id: " gid://shopify/123 ",
|
|
541
|
+
type: "shopify_customer_id",
|
|
542
|
+
collection: "users",
|
|
543
|
+
encoding: "none",
|
|
544
|
+
},
|
|
545
|
+
],
|
|
546
|
+
},
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
expect(result.context?.externalIds).toEqual([
|
|
550
|
+
{
|
|
551
|
+
id: "gid://shopify/123",
|
|
552
|
+
type: "shopify_customer_id",
|
|
553
|
+
collection: "users",
|
|
554
|
+
encoding: "none",
|
|
555
|
+
},
|
|
556
|
+
]);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it("caps app event context external IDs to identity cardinality limits", () => {
|
|
560
|
+
expect(() =>
|
|
561
|
+
appEventTrackSchema.parse({
|
|
562
|
+
event: "Checkout Started",
|
|
563
|
+
origin: "customer",
|
|
564
|
+
context: {
|
|
565
|
+
externalIds: Array.from({ length: 21 }, (_, index) => ({
|
|
566
|
+
id: `customer_${index}`,
|
|
567
|
+
type: "shopify_customer_id",
|
|
568
|
+
collection: "users",
|
|
569
|
+
encoding: "none",
|
|
570
|
+
})),
|
|
571
|
+
},
|
|
572
|
+
}),
|
|
573
|
+
).toThrow();
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it("rejects uppercase external ID types", () => {
|
|
577
|
+
expect(() =>
|
|
578
|
+
appEventTrackSchema.parse({
|
|
579
|
+
event: "Checkout Started",
|
|
580
|
+
origin: "customer",
|
|
581
|
+
context: {
|
|
582
|
+
externalIds: [
|
|
583
|
+
{
|
|
584
|
+
id: "gid://shopify/123",
|
|
585
|
+
type: "Shopify_Customer_Id",
|
|
586
|
+
collection: "users",
|
|
587
|
+
encoding: "none",
|
|
588
|
+
},
|
|
589
|
+
],
|
|
590
|
+
},
|
|
591
|
+
}),
|
|
592
|
+
).toThrow(/lowercase/);
|
|
475
593
|
});
|
|
476
594
|
});
|
package/src/widget-commands.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import * as z from "zod/mini";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
appEventCustomerTrackSchema,
|
|
4
|
+
appEventExternalIdSchema,
|
|
5
|
+
} from "./app-event.js";
|
|
3
6
|
import {
|
|
4
7
|
type ConfigureOptions,
|
|
5
8
|
configureOptionsSchema,
|
|
@@ -50,15 +53,22 @@ const htmlElementOrNullSchema = z.custom<HTMLElement | null>(
|
|
|
50
53
|
);
|
|
51
54
|
|
|
52
55
|
const identifyTraitsSchema = z.record(z.string(), z.unknown());
|
|
56
|
+
const identifyOptionsSchema = z.strictObject({
|
|
57
|
+
externalIds: z.optional(
|
|
58
|
+
z.array(appEventExternalIdSchema).check(z.maxLength(20)),
|
|
59
|
+
),
|
|
60
|
+
});
|
|
53
61
|
const identifyPayloadSchema = z.union([
|
|
54
62
|
z.strictObject({
|
|
55
63
|
kind: z.literal("identify"),
|
|
56
64
|
traits: identifyTraitsSchema,
|
|
65
|
+
options: z.optional(identifyOptionsSchema),
|
|
57
66
|
}),
|
|
58
67
|
z.strictObject({
|
|
59
68
|
kind: z.literal("identify"),
|
|
60
69
|
userId: z.string().check(z.trim(), z.minLength(1)),
|
|
61
70
|
traits: z.optional(identifyTraitsSchema),
|
|
71
|
+
options: z.optional(identifyOptionsSchema),
|
|
62
72
|
}),
|
|
63
73
|
]);
|
|
64
74
|
|
package/src/widget-config.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import * as z from "zod/mini";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
appEventCapabilitySchema,
|
|
4
|
+
appEventExternalIdSchema,
|
|
5
|
+
appEventFlagSchema,
|
|
6
|
+
} from "./app-event.js";
|
|
3
7
|
import { clientMetaSchema } from "./client-meta.js";
|
|
4
8
|
import { DEFAULT_AUTO_DETECT_COLOR_SCHEME_ATTRS } from "./defaults.js";
|
|
5
9
|
import { runtimeEndpointsSchema } from "./runtime-endpoints.js";
|
|
@@ -59,6 +63,9 @@ export const identityConfigInputSchema = z.object({
|
|
|
59
63
|
anonymousId: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
60
64
|
userId: z.optional(z.string().check(z.trim(), z.minLength(1))),
|
|
61
65
|
traits: z.optional(z.record(z.string(), z.unknown())),
|
|
66
|
+
externalIds: z.optional(
|
|
67
|
+
z.array(appEventExternalIdSchema).check(z.maxLength(20)),
|
|
68
|
+
),
|
|
62
69
|
});
|
|
63
70
|
|
|
64
71
|
export type IdentityConfigInput = z.output<typeof identityConfigInputSchema>;
|