@getuserfeedback/protocol 0.7.2 → 0.8.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 +24 -4
- 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/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 +27 -5
- package/src/identity-type.ts +3 -9
- package/src/index.ts +4 -1
- package/src/protocol-root.test.ts +109 -3
- package/src/widget-commands.ts +11 -1
- package/src/widget-config.ts +8 -1
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,26 @@ 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
|
+
*/
|
|
39
|
+
export type AppEventExternalId = {
|
|
40
|
+
id: string;
|
|
41
|
+
type: string;
|
|
42
|
+
collection: "users";
|
|
43
|
+
encoding: "none";
|
|
44
|
+
};
|
|
45
|
+
|
|
35
46
|
/** Page, feature flag, and capability context attached to an event. */
|
|
36
47
|
export type AppEventContext = {
|
|
37
48
|
page?: {
|
|
@@ -41,6 +52,7 @@ export type AppEventContext = {
|
|
|
41
52
|
};
|
|
42
53
|
locale?: string;
|
|
43
54
|
userAgent?: string;
|
|
55
|
+
externalIds?: AppEventExternalId[] | undefined;
|
|
44
56
|
flags?: AppEventFlag[] | undefined;
|
|
45
57
|
capabilities?: AppEventCapability[] | undefined;
|
|
46
58
|
};
|
|
@@ -371,6 +383,7 @@ export type PrerenderCommandPayload = {
|
|
|
371
383
|
export type IdentifyTraitsOnlyCommandPayload = {
|
|
372
384
|
kind: "identify";
|
|
373
385
|
traits: Record<string, unknown>;
|
|
386
|
+
options?: IdentifyOptions | undefined;
|
|
374
387
|
};
|
|
375
388
|
|
|
376
389
|
/** Identify with userId and optional traits (`client.identify(userId, traits?)`). */
|
|
@@ -378,6 +391,7 @@ export type IdentifyUserCommandPayload = {
|
|
|
378
391
|
kind: "identify";
|
|
379
392
|
userId: string;
|
|
380
393
|
traits?: Record<string, unknown> | undefined;
|
|
394
|
+
options?: IdentifyOptions | undefined;
|
|
381
395
|
};
|
|
382
396
|
|
|
383
397
|
/** Identify command payload: either traits-only or userId+traits shape. */
|
|
@@ -385,6 +399,14 @@ export type IdentifyCommandPayload =
|
|
|
385
399
|
| IdentifyTraitsOnlyCommandPayload
|
|
386
400
|
| IdentifyUserCommandPayload;
|
|
387
401
|
|
|
402
|
+
export type IdentifyOptions = {
|
|
403
|
+
externalIds?: AppEventExternalId[] | undefined;
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
export type TrackOptions = {
|
|
407
|
+
externalIds?: AppEventExternalId[] | undefined;
|
|
408
|
+
};
|
|
409
|
+
|
|
388
410
|
/**
|
|
389
411
|
* Command payload for `client.track()`.
|
|
390
412
|
*
|
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,
|
|
@@ -196,6 +196,16 @@ describe("@getuserfeedback/protocol root contract", () => {
|
|
|
196
196
|
traits: {
|
|
197
197
|
email: "user@example.com",
|
|
198
198
|
},
|
|
199
|
+
options: {
|
|
200
|
+
externalIds: [
|
|
201
|
+
{
|
|
202
|
+
id: "gid://shopify/Customer/123",
|
|
203
|
+
type: "shopify_customer_id",
|
|
204
|
+
collection: "users",
|
|
205
|
+
encoding: "none",
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
},
|
|
199
209
|
});
|
|
200
210
|
const withTraitsOnly = parsePublicCommand({
|
|
201
211
|
kind: "identify",
|
|
@@ -210,6 +220,14 @@ describe("@getuserfeedback/protocol root contract", () => {
|
|
|
210
220
|
expect(withUserId.traits).toEqual({
|
|
211
221
|
email: "user@example.com",
|
|
212
222
|
});
|
|
223
|
+
expect(withUserId.options?.externalIds).toEqual([
|
|
224
|
+
{
|
|
225
|
+
id: "gid://shopify/Customer/123",
|
|
226
|
+
type: "shopify_customer_id",
|
|
227
|
+
collection: "users",
|
|
228
|
+
encoding: "none",
|
|
229
|
+
},
|
|
230
|
+
]);
|
|
213
231
|
}
|
|
214
232
|
|
|
215
233
|
expect(withTraitsOnly.kind).toBe("identify");
|
|
@@ -468,9 +486,97 @@ describe("@getuserfeedback/protocol root contract", () => {
|
|
|
468
486
|
);
|
|
469
487
|
});
|
|
470
488
|
|
|
471
|
-
it("
|
|
472
|
-
|
|
489
|
+
it("rejects legacy custom app event identity types", () => {
|
|
490
|
+
expect(() =>
|
|
491
|
+
appEventIdentityTypeSchema.parse("custom.shopifyCustomerId"),
|
|
492
|
+
).toThrow(/context\.externalIds/);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it("accepts Segment-compatible external IDs in app event context", () => {
|
|
496
|
+
const result = appEventTrackSchema.parse({
|
|
497
|
+
event: "Checkout Started",
|
|
498
|
+
origin: "customer",
|
|
499
|
+
context: {
|
|
500
|
+
externalIds: [
|
|
501
|
+
{
|
|
502
|
+
id: "gid://shopify/123",
|
|
503
|
+
type: "shopify_customer_id",
|
|
504
|
+
collection: "users",
|
|
505
|
+
encoding: "none",
|
|
506
|
+
},
|
|
507
|
+
],
|
|
508
|
+
},
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
expect(result.context?.externalIds).toEqual([
|
|
512
|
+
{
|
|
513
|
+
id: "gid://shopify/123",
|
|
514
|
+
type: "shopify_customer_id",
|
|
515
|
+
collection: "users",
|
|
516
|
+
encoding: "none",
|
|
517
|
+
},
|
|
518
|
+
]);
|
|
519
|
+
});
|
|
473
520
|
|
|
474
|
-
|
|
521
|
+
it("normalizes external ID values before app event context output", () => {
|
|
522
|
+
const result = appEventTrackSchema.parse({
|
|
523
|
+
event: "Checkout Started",
|
|
524
|
+
origin: "customer",
|
|
525
|
+
context: {
|
|
526
|
+
externalIds: [
|
|
527
|
+
{
|
|
528
|
+
id: " gid://shopify/123 ",
|
|
529
|
+
type: "shopify_customer_id",
|
|
530
|
+
collection: "users",
|
|
531
|
+
encoding: "none",
|
|
532
|
+
},
|
|
533
|
+
],
|
|
534
|
+
},
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
expect(result.context?.externalIds).toEqual([
|
|
538
|
+
{
|
|
539
|
+
id: "gid://shopify/123",
|
|
540
|
+
type: "shopify_customer_id",
|
|
541
|
+
collection: "users",
|
|
542
|
+
encoding: "none",
|
|
543
|
+
},
|
|
544
|
+
]);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it("caps app event context external IDs to identity cardinality limits", () => {
|
|
548
|
+
expect(() =>
|
|
549
|
+
appEventTrackSchema.parse({
|
|
550
|
+
event: "Checkout Started",
|
|
551
|
+
origin: "customer",
|
|
552
|
+
context: {
|
|
553
|
+
externalIds: Array.from({ length: 21 }, (_, index) => ({
|
|
554
|
+
id: `customer_${index}`,
|
|
555
|
+
type: "shopify_customer_id",
|
|
556
|
+
collection: "users",
|
|
557
|
+
encoding: "none",
|
|
558
|
+
})),
|
|
559
|
+
},
|
|
560
|
+
}),
|
|
561
|
+
).toThrow();
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it("rejects uppercase external ID types", () => {
|
|
565
|
+
expect(() =>
|
|
566
|
+
appEventTrackSchema.parse({
|
|
567
|
+
event: "Checkout Started",
|
|
568
|
+
origin: "customer",
|
|
569
|
+
context: {
|
|
570
|
+
externalIds: [
|
|
571
|
+
{
|
|
572
|
+
id: "gid://shopify/123",
|
|
573
|
+
type: "Shopify_Customer_Id",
|
|
574
|
+
collection: "users",
|
|
575
|
+
encoding: "none",
|
|
576
|
+
},
|
|
577
|
+
],
|
|
578
|
+
},
|
|
579
|
+
}),
|
|
580
|
+
).toThrow(/lowercase/);
|
|
475
581
|
});
|
|
476
582
|
});
|
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>;
|