@getuserfeedback/protocol 0.6.1 → 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.
Files changed (59) hide show
  1. package/dist/app-event.d.ts +779 -10
  2. package/dist/app-event.d.ts.map +1 -1
  3. package/dist/app-event.js +81 -8
  4. package/dist/flow-assignments.d.ts +7 -4
  5. package/dist/flow-assignments.d.ts.map +1 -1
  6. package/dist/flow-assignments.js +15 -5
  7. package/dist/host/constants.d.ts +2 -2
  8. package/dist/host/constants.d.ts.map +1 -1
  9. package/dist/host/constants.js +3 -1
  10. package/dist/host/external-id-argument-guards.d.ts +10 -0
  11. package/dist/host/external-id-argument-guards.d.ts.map +1 -0
  12. package/dist/host/external-id-argument-guards.js +27 -0
  13. package/dist/host/index.d.ts +1 -0
  14. package/dist/host/index.d.ts.map +1 -1
  15. package/dist/host/index.js +1 -0
  16. package/dist/host/sdk-types.d.ts +92 -27
  17. package/dist/host/sdk-types.d.ts.map +1 -1
  18. package/dist/host/sdk.d.ts.map +1 -1
  19. package/dist/identity-type.d.ts +2 -1
  20. package/dist/identity-type.d.ts.map +1 -1
  21. package/dist/identity-type.js +1 -1
  22. package/dist/index.d.ts +3 -87
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1 -6
  25. package/dist/widget-commands.d.ts +124 -3
  26. package/dist/widget-commands.d.ts.map +1 -1
  27. package/dist/widget-commands.js +10 -0
  28. package/dist/widget-config.d.ts +23 -5
  29. package/dist/widget-config.d.ts.map +1 -1
  30. package/dist/widget-config.js +2 -1
  31. package/package.json +22 -2
  32. package/src/app-event.ts +235 -0
  33. package/src/client-meta.ts +25 -0
  34. package/src/defaults.ts +4 -0
  35. package/src/errors.ts +58 -0
  36. package/src/flow-assignments.test.ts +82 -0
  37. package/src/flow-assignments.ts +151 -0
  38. package/src/host/command-dispatch.ts +59 -0
  39. package/src/host/command-envelope.ts +41 -0
  40. package/src/host/command-settlement.ts +121 -0
  41. package/src/host/constants.ts +103 -0
  42. package/src/host/external-id-argument-guards.ts +57 -0
  43. package/src/host/host-event-contract.ts +277 -0
  44. package/src/host/index.ts +14 -0
  45. package/src/host/lazy-handle-client.ts +207 -0
  46. package/src/host/request-id.ts +3 -0
  47. package/src/host/sdk-types.ts +528 -0
  48. package/src/host/sdk.ts +43 -0
  49. package/src/host/unique-id.ts +17 -0
  50. package/src/identity-type.ts +96 -0
  51. package/src/index.ts +139 -0
  52. package/src/protocol-root.test.ts +582 -0
  53. package/src/public-grant-scope.ts +25 -0
  54. package/src/runtime-endpoints.ts +69 -0
  55. package/src/scopes.ts +79 -0
  56. package/src/trpc-envelope.ts +9 -0
  57. package/src/version-resolution.ts +18 -0
  58. package/src/widget-commands.ts +162 -0
  59. package/src/widget-config.ts +164 -0
@@ -0,0 +1,528 @@
1
+ /**
2
+ * getuserfeedback widget SDK public API.
3
+ *
4
+ * {@link FlowState} lives in `./host-event-contract` (re-exported from the `@getuserfeedback/protocol/host` barrel).
5
+ */
6
+
7
+ import type { Scope } from "../scopes.js";
8
+
9
+ // ── Events and identity ─────────────────────────────────────────────
10
+
11
+ /**
12
+ * Identifier types supported by event tracking and identity resolution.
13
+ *
14
+ * Business-specific identifiers are sent as Segment-compatible
15
+ * `context.externalIds` entries on app events or identify options.
16
+ *
17
+ * @see https://getuserfeedback.com/docs/guides/identity-resolution
18
+ */
19
+ export type AppEventIdentityType =
20
+ | "anonymousId"
21
+ | "userId"
22
+ | "traits.email"
23
+ | "traits.phone"
24
+ | "context.device.id"
25
+ | "context.device.advertisingId"
26
+ | "context.device.token";
27
+
28
+ /** A single standard identifier, such as a user ID, anonymous ID, email, or device ID. */
29
+ export type AppEventIdentity = {
30
+ type: AppEventIdentityType;
31
+ value: string;
32
+ };
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
+
46
+ /** Page, feature flag, and capability context attached to an event. */
47
+ export type AppEventContext = {
48
+ page?: {
49
+ path?: string;
50
+ referrer?: string;
51
+ search?: string;
52
+ };
53
+ locale?: string;
54
+ userAgent?: string;
55
+ externalIds?: AppEventExternalId[] | undefined;
56
+ flags?: AppEventFlag[] | undefined;
57
+ capabilities?: AppEventCapability[] | undefined;
58
+ };
59
+
60
+ /** Supported feature flag value shape. */
61
+ export type AppEventFlagValue =
62
+ | string
63
+ | number
64
+ | boolean
65
+ | null
66
+ | AppEventFlagValue[]
67
+ | { [key: string]: AppEventFlagValue };
68
+
69
+ /**
70
+ * Supported event property value shape.
71
+ *
72
+ * @see https://getuserfeedback.com/docs/reference/events
73
+ */
74
+ export type AppEventJsonValue = AppEventFlagValue;
75
+
76
+ /** Feature flag provider metadata. */
77
+ export type AppEventFlagProvider = {
78
+ name: string;
79
+ project?: string | undefined;
80
+ environment?: string | undefined;
81
+ };
82
+
83
+ /** Feature flag evaluation context attached to widget initialization or events. */
84
+ export type AppEventFlag = {
85
+ key: string;
86
+ target?: string | undefined;
87
+ value: AppEventFlagValue;
88
+ variant?: string | undefined;
89
+ origin?: "runtime_config" | "trigger_context" | "provider_sync" | "api";
90
+ provider?: AppEventFlagProvider | undefined;
91
+ status?: string | undefined;
92
+ reason?: string | undefined;
93
+ evaluatedAt?: string | undefined;
94
+ };
95
+
96
+ /** Host capability context attached to widget initialization or events. */
97
+ export type AppEventCapability = {
98
+ key: string;
99
+ source?: string | undefined;
100
+ provider?: AppEventFlagProvider | undefined;
101
+ };
102
+
103
+ /** Feature flag evaluations accepted by SDK initialization. */
104
+ export type FlagsInput = AppEventFlag[] | Record<string, AppEventFlagValue>;
105
+
106
+ /** Host capabilities accepted by SDK initialization. */
107
+ export type CapabilitiesInput = Array<string | AppEventCapability>;
108
+
109
+ /** Optional fields shared by event payloads. */
110
+ export type AppEventBase = {
111
+ traits?: Record<string, unknown> | undefined;
112
+ identities?: AppEventIdentity[] | undefined;
113
+ context?: AppEventContext | undefined;
114
+ messageId?: string | undefined;
115
+ timestamp?: number | undefined;
116
+ };
117
+
118
+ /** Reference to a specific survey or flow. */
119
+ export type AppEventSurveyReference = {
120
+ surveyId: string;
121
+ };
122
+
123
+ /** Event payload emitted when a flow is shown. */
124
+ export type FlowViewedPayload = AppEventBase & {
125
+ origin?: "system" | undefined;
126
+ event: "Flow Viewed";
127
+ references: AppEventSurveyReference;
128
+ };
129
+
130
+ /** Event payload emitted when a user dismisses a flow. */
131
+ export type FlowDismissedPayload = AppEventBase & {
132
+ origin?: "system" | undefined;
133
+ event: "Flow Dismissed";
134
+ references: AppEventSurveyReference;
135
+ };
136
+
137
+ /**
138
+ * Event payload sent with `client.track()`.
139
+ *
140
+ * @see https://getuserfeedback.com/docs/reference/events
141
+ */
142
+ export type CustomerTrackPayload = AppEventBase & {
143
+ origin: "customer";
144
+ event: string;
145
+ properties?: Record<string, AppEventJsonValue> | undefined;
146
+ };
147
+
148
+ /**
149
+ * Event payload union. Use `origin` as the discriminator when handling the
150
+ * union directly.
151
+ */
152
+ export type AppEventPayload =
153
+ | FlowViewedPayload
154
+ | FlowDismissedPayload
155
+ | CustomerTrackPayload;
156
+
157
+ // ── Client metadata ─────────────────────────────────────────────────
158
+
159
+ /** How the widget was loaded, such as SDK, GTM, Segment, or a custom loader. */
160
+ export type ClientMetaLoader =
161
+ | "sdk"
162
+ | "gtm"
163
+ | "segment"
164
+ | "rudderstack"
165
+ | "tealium"
166
+ | "custom";
167
+
168
+ /** Delivery method for the widget script. */
169
+ export type ClientMetaTransport =
170
+ | "script-tag"
171
+ | "esm"
172
+ | "loader"
173
+ | "tag-manager"
174
+ | "snippet";
175
+
176
+ /** Detected or configured front-end framework. */
177
+ export type ClientMetaRuntimeFramework = "next" | "vite" | "webpack" | "plain";
178
+
179
+ /** Runtime environment (browser or edge). */
180
+ export type ClientMetaRuntimeEnv = "browser" | "edge";
181
+
182
+ /** Metadata about the client integration, used for diagnostics and support. */
183
+ export type ClientMeta = {
184
+ loader: ClientMetaLoader;
185
+ clientName?: string | undefined;
186
+ clientVersion?: string | undefined;
187
+ integrator?:
188
+ | {
189
+ name?: string | undefined;
190
+ version?: string | undefined;
191
+ }
192
+ | undefined;
193
+ transport?: ClientMetaTransport | undefined;
194
+ runtime?:
195
+ | {
196
+ framework?: ClientMetaRuntimeFramework | undefined;
197
+ runtime?: ClientMetaRuntimeEnv | undefined;
198
+ }
199
+ | undefined;
200
+ notes?: string | undefined;
201
+ };
202
+
203
+ export type RuntimeEndpoints = {
204
+ apiUrl?: string | undefined;
205
+ coreUrl?: string | undefined;
206
+ };
207
+
208
+ /** A single response metadata tag value preserved with a submitted response. */
209
+ export type ResponseMetadataTagValue = string | string[];
210
+
211
+ /** Optional metadata tags the host can attach to a widget flow run. */
212
+ export type ResponseMetadataInput = {
213
+ tags?: Record<string, ResponseMetadataTagValue> | undefined;
214
+ };
215
+
216
+ // ── Configuration (color scheme, consent, auth) ─────────────────────────────
217
+
218
+ /**
219
+ * Use host attributes to auto-detect the color scheme ("light" by default). The widget observes these attributes on body and html elements,
220
+ *
221
+ * class="dark" or data-theme="dark" on html or body elements will set the widget to dark mode.
222
+ *
223
+ * class="light" or data-theme="light" will set the widget to light mode.
224
+ *
225
+ * class="system" or data-theme="system" will set the widget to follow the user's OS/browser preference (prefers-color-scheme).
226
+ */
227
+ export type ColorSchemeAutoDetect = {
228
+ /** List of host attribute names (html or body) to observe for color scheme.
229
+ *
230
+ * Default: ["class", "data-theme"]
231
+ */
232
+ autoDetectColorScheme: string[];
233
+ };
234
+
235
+ /**
236
+ * Color scheme: either auto-detect from host html and body attributes (default) or provide an explicit value.
237
+ * - { autoDetectColorScheme: ["class", "data-theme"] }
238
+ * - "light" | "dark" | "system"
239
+ */
240
+ export type ColorSchemeConfig =
241
+ | ColorSchemeAutoDetect
242
+ | "light"
243
+ | "dark"
244
+ | "system";
245
+
246
+ /** Whether telemetry is enabled. */
247
+ export type TelemetryConfig = {
248
+ enabled?: boolean | undefined;
249
+ };
250
+
251
+ /** When true, prints debug logs to console. Narrow down with a namespace string/array eg. `["loader", "handshake"]`. Default `false`. */
252
+ export type DebugConfig = boolean | string | string[];
253
+
254
+ /** Status of a consent decision (pending, granted, denied, revoked). */
255
+ export type ConsentStatus = "pending" | "granted" | "denied" | "revoked";
256
+
257
+ /**
258
+ * Supported consent scope identifiers for explicit allow-list grants (full CMP-aligned set).
259
+ * Alias for {@link Scope}; kept for backward-compatible imports.
260
+ */
261
+ export type GrantScope = Scope;
262
+
263
+ /** Consent configuration. Use a single decision for all scopes, or provide an explicit allow-list of granted scopes. */
264
+ export type ConsentConfig = ConsentStatus | Scope[];
265
+
266
+ /** Auth configuration, such as a JWT for authenticated flows. */
267
+ export type AuthConfig = {
268
+ jwt?: { token: string } | null | undefined;
269
+ };
270
+
271
+ /**
272
+ * Runtime configuration updates accepted by `client.configure()`.
273
+ *
274
+ * @see https://getuserfeedback.com/docs/security-and-privacy/compliance-and-consent
275
+ */
276
+ export type ConfigureOptions = {
277
+ colorScheme?: ColorSchemeConfig | undefined;
278
+ consent?: ConsentConfig | undefined;
279
+ auth?: AuthConfig | undefined;
280
+ };
281
+
282
+ /** @internal Options when initializing the widget: API key, color scheme, disableTelemetry, enableDebug, defaultConsent, capabilities, and optional host targeting context. */
283
+ export type InitOptions = {
284
+ apiKey: string;
285
+ colorScheme?: ColorSchemeConfig | undefined;
286
+ disableTelemetry?: boolean | undefined;
287
+ enableDebug?: DebugConfig | undefined;
288
+ defaultConsent?: ConsentConfig | undefined;
289
+ clientMeta?: ClientMeta | undefined;
290
+ capabilities?: string[] | undefined;
291
+ flags?: AppEventFlag[] | undefined;
292
+ hostCapabilities?: AppEventCapability[] | undefined;
293
+ runtimeEndpoints?: RuntimeEndpoints | undefined;
294
+ };
295
+
296
+ export type ClientOptions = {
297
+ /** Your project API key. */
298
+ apiKey: string;
299
+ /** Color scheme config: host-driven (loader observes attributes) or explicit. */
300
+ colorScheme?: ColorSchemeConfig | undefined;
301
+ /**
302
+ * Initial consent state.
303
+ *
304
+ * Default: `granted` (all scopes).
305
+ *
306
+ * Accepts either a single decision (`granted` | `denied` | `pending` | `revoked`)
307
+ * or an explicit allow-list of additional granted scopes (`Scope[]`).
308
+ *
309
+ * For strict privacy guarantees, use `pending` at init time and later call `client.configure({ consent })` after
310
+ * the user makes a consent choice via your CMP or UI.
311
+ *
312
+ * In array form, listed scopes are granted and all other non-essential scopes are denied.
313
+ *
314
+ * Essential baseline scopes are always granted for core widget operation and cannot be denied:
315
+ * - `functionality.storage`
316
+ * - `security.storage`
317
+ *
318
+ * @see https://getuserfeedback.com/docs/security-and-privacy/compliance-and-consent
319
+ */
320
+ defaultConsent?: ConsentConfig | undefined;
321
+ /** When true, prints debug logs to console. Narrow down with a namespace string/array eg. ```["loader", "handshake"]```. Default `false`.
322
+ */
323
+ enableDebug?: DebugConfig | undefined;
324
+ /** When true, the widget does not load automatically; call `client.load()` when ready. Default `false` */
325
+ disableAutoLoad?: boolean;
326
+ /** Disable anonymous widget telemetry.
327
+ *
328
+ * When `true`, telemetry is not sent.
329
+ *
330
+ * This flag does not disable user targeting and reporting analytics. It's used (by us) for performance monitoring and error tracking.
331
+ *
332
+ * Included telemetry data when enabled: apiKey, ephemeral one-time session id,
333
+ * execution flow, event timestamps, and page origin (domain name).
334
+ *
335
+ * Excluded by default: user identity and detailed page fields (path/referrer/search).
336
+ */
337
+ disableTelemetry?: boolean | undefined;
338
+ /** Feature flag evaluations collected with widget initialization. */
339
+ flags?: FlagsInput | undefined;
340
+ /** Host capabilities collected with widget initialization. */
341
+ capabilities?: CapabilitiesInput | undefined;
342
+ _loaderUrl?: string | undefined;
343
+ _coreUrl?: string | undefined;
344
+ _apiUrl?: string | undefined;
345
+ };
346
+
347
+ // ── Flow and widget actions (used by Client methods) ───────────────────
348
+
349
+ /** Open a flow with a given instance. Omit flowHandleId for first open; core returns it in settlement. */
350
+ export type OpenCommandPayload = {
351
+ kind: "open";
352
+ flowId: string;
353
+ flowHandleId?: string | undefined;
354
+ container?: HTMLElement | null | undefined;
355
+ /** Optional metadata tags to persist with the eventual response submission. */
356
+ metadata?: ResponseMetadataInput | undefined;
357
+ /**
358
+ * When `true`, the flow view does not show a close button.
359
+ * Useful for mobile or embedded contexts where the host handles dismissal (e.g. via a drawer or back gesture).
360
+ */
361
+ hideCloseButton?: boolean | undefined;
362
+ };
363
+
364
+ /** Prefetch a flow. Omit flowHandleId for first prefetch; core returns it in settlement. */
365
+ export type PrefetchCommandPayload = {
366
+ kind: "prefetch";
367
+ flowId: string;
368
+ flowHandleId?: string | undefined;
369
+ };
370
+
371
+ /** Prerender a flow. Omit flowHandleId for first prerender; core returns it in settlement. */
372
+ export type PrerenderCommandPayload = {
373
+ kind: "prerender";
374
+ flowId: string;
375
+ flowHandleId?: string | undefined;
376
+ /**
377
+ * When `true`, the prerendered view does not show a close button.
378
+ */
379
+ hideCloseButton?: boolean | undefined;
380
+ };
381
+
382
+ /** Identify with traits only (`client.identify(traits)`). */
383
+ export type IdentifyTraitsOnlyCommandPayload = {
384
+ kind: "identify";
385
+ traits: Record<string, unknown>;
386
+ options?: IdentifyOptions | undefined;
387
+ };
388
+
389
+ /** Identify with userId and optional traits (`client.identify(userId, traits?)`). */
390
+ export type IdentifyUserCommandPayload = {
391
+ kind: "identify";
392
+ userId: string;
393
+ traits?: Record<string, unknown> | undefined;
394
+ options?: IdentifyOptions | undefined;
395
+ };
396
+
397
+ /** Identify command payload: either traits-only or userId+traits shape. */
398
+ export type IdentifyCommandPayload =
399
+ | IdentifyTraitsOnlyCommandPayload
400
+ | IdentifyUserCommandPayload;
401
+
402
+ export type IdentifyOptions = {
403
+ externalIds?: AppEventExternalId[] | undefined;
404
+ };
405
+
406
+ export type TrackOptions = {
407
+ externalIds?: AppEventExternalId[] | undefined;
408
+ };
409
+
410
+ /**
411
+ * Command payload for `client.track()`.
412
+ *
413
+ * @see https://getuserfeedback.com/docs/reference/events
414
+ */
415
+ export type TrackCommandPayload = AppEventBase & {
416
+ kind: "track";
417
+ origin: "customer";
418
+ event: string;
419
+ properties?: Record<string, AppEventJsonValue> | undefined;
420
+ };
421
+
422
+ /** Attach or detach a resolved flow handle from a host container. */
423
+ export type SetContainerCommandPayload = {
424
+ kind: "setContainer";
425
+ flowHandleId: string;
426
+ container: HTMLElement | null;
427
+ };
428
+
429
+ /** Default container behavior for flows in an instance. */
430
+ export type ContainerPolicy =
431
+ | {
432
+ kind: "floating";
433
+ }
434
+ | {
435
+ kind: "hostContainer";
436
+ host: HTMLElement | null;
437
+ sharing: "shared" | "perFlowRun";
438
+ };
439
+
440
+ /** Set the instance-level default container policy. */
441
+ export type SetDefaultContainerPolicyCommandPayload = {
442
+ kind: "setDefaultContainerPolicy";
443
+ policy: ContainerPolicy;
444
+ };
445
+
446
+ /** Update color scheme, consent, and auth. */
447
+ export type ConfigureCommandPayload = {
448
+ kind: "configure";
449
+ opts: ConfigureOptions;
450
+ };
451
+
452
+ /** Initialize the widget with options. */
453
+ export type InitCommandPayload = {
454
+ kind: "init";
455
+ opts: InitOptions;
456
+ };
457
+
458
+ /** Close a flow. */
459
+ export type CloseCommandPayload = {
460
+ kind: "close";
461
+ flowHandleId?: string | undefined;
462
+ };
463
+
464
+ /** Reset widget state. */
465
+ export type ResetCommandPayload = {
466
+ kind: "reset";
467
+ };
468
+
469
+ /** Union of widget action payloads such as open, prefetch, identify, configure, and track. */
470
+ export type PublicCommandPayload =
471
+ | OpenCommandPayload
472
+ | PrefetchCommandPayload
473
+ | PrerenderCommandPayload
474
+ | IdentifyCommandPayload
475
+ | SetContainerCommandPayload
476
+ | SetDefaultContainerPolicyCommandPayload
477
+ | ConfigureCommandPayload
478
+ | InitCommandPayload
479
+ | CloseCommandPayload
480
+ | ResetCommandPayload
481
+ | TrackCommandPayload;
482
+
483
+ /** Input for a single widget action; same shape as {@link PublicCommandPayload}. */
484
+ export type Command = PublicCommandPayload;
485
+
486
+ /** Wrapper for a widget action (version, request id, idempotency key, optional client metadata, and the action payload). */
487
+ export type CommandEnvelope = {
488
+ version: "1";
489
+ instanceId?: string | undefined;
490
+ requestId: string;
491
+ idempotencyKey: string;
492
+ clientMeta?: ClientMeta | undefined;
493
+ command: PublicCommandPayload;
494
+ };
495
+
496
+ // ── Telemetry (use("beforeSend")) ─────────────────────────────────────
497
+
498
+ /** Identity kind for telemetry events; same as {@link AppEventIdentityType}. */
499
+ export type TelemetryIdentityType = AppEventIdentityType;
500
+
501
+ /**
502
+ * Event passed to the `beforeSend` hook in {@link Client.use}.
503
+ * When `debug` is true, identities and full page context are included; when false, no identities and page is origin-only.
504
+ *
505
+ * TODO(migration): Rename this SDK-facing type to WidgetTelemetryEvent in the
506
+ * same change that updates SDK imports, so the generic TelemetryEvent name can
507
+ * retire here without colliding with shared telemetry.
508
+ * [from=TelemetryEvent-widget-surface] [to=WidgetTelemetryEvent] [scope=slice] [priority=low]
509
+ */
510
+ export type TelemetryEvent = {
511
+ type: string;
512
+ timestamp: number;
513
+ apiKey: string;
514
+ /** When true, identities and full page context are present; when false, no identities and page.origin only. */
515
+ debug: boolean;
516
+ identities: Array<{ type: TelemetryIdentityType; value: string }>;
517
+ context: {
518
+ page: {
519
+ path?: string | null | undefined;
520
+ referrer?: string | null | undefined;
521
+ search?: string | null | undefined;
522
+ origin?: string | null | undefined;
523
+ };
524
+ widgetVersion: string;
525
+ clientMeta?: ClientMeta | undefined;
526
+ };
527
+ properties?: Record<string, unknown> | undefined;
528
+ };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * SDK-facing protocol surface: types (from sdk-types) and values only.
3
+ * Does not re-export schema modules so SDK dist stays zod-free.
4
+ */
5
+
6
+ export { createCommandDispatch } from "./command-dispatch.js";
7
+ export {
8
+ COMMAND_SETTLEMENT_TIMEOUT_CODE,
9
+ CommandSettlementError,
10
+ CommandSettlementTimeoutError,
11
+ toCommandSettlementError,
12
+ waitForCommandSettlement,
13
+ } from "./command-settlement.js";
14
+ export type {
15
+ FlowHandleFromSettlement,
16
+ FlowState,
17
+ } from "./host-event-contract.js";
18
+ export {
19
+ getFlowHandleFromSettlementDetail,
20
+ isFlowStateChangedDetail,
21
+ isHandleInvalidatedDetail,
22
+ isInstanceFlowStateChangedDetail,
23
+ } from "./host-event-contract.js";
24
+ export type {
25
+ AppEventCapability,
26
+ AppEventFlag,
27
+ AppEventFlagProvider,
28
+ AppEventFlagValue,
29
+ AppEventPayload,
30
+ CapabilitiesInput,
31
+ ClientMeta,
32
+ ClientOptions,
33
+ Command,
34
+ CommandEnvelope,
35
+ ConfigureOptions,
36
+ ContainerPolicy,
37
+ FlagsInput,
38
+ InitOptions,
39
+ PublicCommandPayload,
40
+ ResponseMetadataInput,
41
+ // Re-export name stays TelemetryEvent until the migration on TelemetryEvent in sdk-types completes.
42
+ TelemetryEvent,
43
+ } from "./sdk-types.js";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Generates a unique string ID. Uses crypto.randomUUID when available,
3
+ * otherwise falls back to a timestamp + random string.
4
+ *
5
+ * @param prefix - Optional prefix for the fallback format (e.g. "req", "err").
6
+ * Only affects fallback; UUIDs have no prefix.
7
+ */
8
+ export function createUniqueId(prefix?: string): string {
9
+ if (
10
+ typeof globalThis.crypto !== "undefined" &&
11
+ typeof globalThis.crypto.randomUUID === "function"
12
+ ) {
13
+ return globalThis.crypto.randomUUID();
14
+ }
15
+ const fallback = `${Date.now()}_${Math.random().toString(36).slice(2)}`;
16
+ return prefix ? `${prefix}_${fallback}` : fallback;
17
+ }
@@ -0,0 +1,96 @@
1
+ import * as z from "zod/mini";
2
+
3
+ export const IDENTITY_TYPE_VALUES = [
4
+ "anonymousId",
5
+ "userId",
6
+ "traits.email",
7
+ "traits.phone",
8
+ "context.device.id",
9
+ "context.device.advertisingId",
10
+ "context.device.token",
11
+ ] as const;
12
+
13
+ export type StandardIdentityTypeValue = (typeof IDENTITY_TYPE_VALUES)[number];
14
+ export type IdentityTypeValue = StandardIdentityTypeValue;
15
+
16
+ const IDENTITY_TYPE_ALIAS_SUGGESTIONS: Record<
17
+ string,
18
+ StandardIdentityTypeValue
19
+ > = {
20
+ advertising_id: "context.device.advertisingId",
21
+ anonymous_id: "anonymousId",
22
+ device_id: "context.device.id",
23
+ device_token: "context.device.token",
24
+ email: "traits.email",
25
+ phone: "traits.phone",
26
+ phone_number: "traits.phone",
27
+ user_id: "userId",
28
+ };
29
+
30
+ const COMMON_IDENTITY_TYPE_MAPPINGS = [
31
+ ["email", "traits.email"],
32
+ ["phone", "traits.phone"],
33
+ ["user_id", "userId"],
34
+ ] as const;
35
+
36
+ const normalizeIdentityTypeAliasKey = (value: string): string =>
37
+ value.trim().toLowerCase().replace(/-/g, "_");
38
+
39
+ export const isIdentityTypeValue = (
40
+ value: string,
41
+ ): value is IdentityTypeValue =>
42
+ IDENTITY_TYPE_VALUES.some((candidate) => candidate === value);
43
+
44
+ const parseIdentityTypeValue = (value: string): IdentityTypeValue | null =>
45
+ isIdentityTypeValue(value) ? value : null;
46
+
47
+ export const getIdentityTypeSuggestion = (
48
+ value: string,
49
+ ): IdentityTypeValue | null =>
50
+ IDENTITY_TYPE_ALIAS_SUGGESTIONS[normalizeIdentityTypeAliasKey(value)] ?? null;
51
+
52
+ export const buildIdentityTypeGuidanceMessage = (value: string): string => {
53
+ const normalizedValue = value.trim();
54
+ const displayValue =
55
+ normalizedValue.length > 0 ? `"${normalizedValue}"` : "that value";
56
+ const suggestion = getIdentityTypeSuggestion(value);
57
+
58
+ if (suggestion) {
59
+ return `Unsupported identity type ${displayValue}. Use "${suggestion}" instead.`;
60
+ }
61
+
62
+ const mappingExamples = COMMON_IDENTITY_TYPE_MAPPINGS.map(
63
+ ([from, to]) => `${from} -> ${to}`,
64
+ ).join(", ");
65
+
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}.`;
67
+ };
68
+
69
+ const identityTypeInputSchema = z.string().check(
70
+ z.trim(),
71
+ z.minLength(1, { error: "Identity type is required." }),
72
+ z.check<string>((ctx) => {
73
+ if (isIdentityTypeValue(ctx.value)) {
74
+ return;
75
+ }
76
+ ctx.issues.push({
77
+ code: "custom",
78
+ input: ctx.value,
79
+ message: buildIdentityTypeGuidanceMessage(ctx.value),
80
+ continue: false,
81
+ });
82
+ }),
83
+ );
84
+
85
+ export const appEventIdentityTypeSchema = z.pipe(
86
+ identityTypeInputSchema,
87
+ z.transform((value): IdentityTypeValue => {
88
+ const parsed = parseIdentityTypeValue(value);
89
+ if (parsed === null) {
90
+ throw new Error(
91
+ `Identity type should have been validated before transform: ${value}`,
92
+ );
93
+ }
94
+ return parsed;
95
+ }),
96
+ );