@getuserfeedback/protocol 0.6.1 → 0.7.2

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