@wireai/activation 0.15.0 → 0.16.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 (67) hide show
  1. package/AGENTS.md +95 -20
  2. package/CHANGELOG.md +707 -0
  3. package/INTEGRATION_PROMPT.md +61 -23
  4. package/README.md +100 -25
  5. package/dist/analytics/index.d.mts +32 -10
  6. package/dist/analytics/index.d.ts +32 -10
  7. package/dist/analytics/index.js +288 -127
  8. package/dist/analytics/index.js.map +1 -1
  9. package/dist/analytics/index.mjs +288 -127
  10. package/dist/analytics/index.mjs.map +1 -1
  11. package/dist/coachmarks/index.d.mts +14 -0
  12. package/dist/coachmarks/index.d.ts +14 -0
  13. package/dist/coachmarks/index.js +73 -20
  14. package/dist/coachmarks/index.js.map +1 -1
  15. package/dist/coachmarks/index.mjs +73 -20
  16. package/dist/coachmarks/index.mjs.map +1 -1
  17. package/dist/{currentSession-orZy5p1e.d.mts → currentSession-Bz7G6lno.d.mts} +25 -35
  18. package/dist/{currentSession-CFSRZ2wg.d.ts → currentSession-z-CZ55ad.d.ts} +25 -35
  19. package/dist/index.d.mts +5 -2
  20. package/dist/index.d.ts +5 -2
  21. package/dist/index.js +125 -36
  22. package/dist/index.js.map +1 -1
  23. package/dist/index.mjs +125 -36
  24. package/dist/index.mjs.map +1 -1
  25. package/dist/questionnaire/index.d.mts +0 -13
  26. package/dist/questionnaire/index.d.ts +0 -13
  27. package/dist/questionnaire/index.js +154 -43
  28. package/dist/questionnaire/index.js.map +1 -1
  29. package/dist/questionnaire/index.mjs +155 -44
  30. package/dist/questionnaire/index.mjs.map +1 -1
  31. package/dist/reviews/index.js +159 -91
  32. package/dist/reviews/index.js.map +1 -1
  33. package/dist/reviews/index.mjs +160 -92
  34. package/dist/reviews/index.mjs.map +1 -1
  35. package/dist/showcase/index.js +59 -18
  36. package/dist/showcase/index.js.map +1 -1
  37. package/dist/showcase/index.mjs +60 -19
  38. package/dist/showcase/index.mjs.map +1 -1
  39. package/llms.txt +9 -9
  40. package/package.json +6 -9
  41. package/src/analytics/currentSession.ts +141 -4
  42. package/src/analytics/index.ts +6 -1
  43. package/src/analytics/reportClientEvent.ts +19 -10
  44. package/src/analytics/useAnalytics.ts +74 -15
  45. package/src/analytics/wireDoctor.ts +152 -7
  46. package/src/coachmarks/CoachmarkProvider.tsx +26 -5
  47. package/src/coachmarks/runtime.ts +53 -0
  48. package/src/coachmarks/useCoachmarkTour.ts +51 -1
  49. package/src/context/deviceId.ts +49 -15
  50. package/src/features/WireFeaturesProvider.tsx +72 -12
  51. package/src/features/fetchWireFeatures.ts +49 -11
  52. package/src/features/useWireFeatures.ts +39 -3
  53. package/src/identity/identityRecord.ts +15 -2
  54. package/src/questionnaire/QuestionnaireGate.tsx +40 -1
  55. package/src/questionnaire/transport.ts +22 -8
  56. package/src/questionnaire/useQuestionnaireGate.ts +58 -7
  57. package/src/reviews/ReviewGate.tsx +39 -0
  58. package/src/reviews/idempotency.ts +38 -0
  59. package/src/reviews/runtime.ts +39 -10
  60. package/src/reviews/transport.ts +22 -8
  61. package/src/reviews/useReviewGate.ts +57 -7
  62. package/src/session-analytics/lifecycle.ts +16 -0
  63. package/src/session-analytics/useLifecycleEvents.ts +30 -2
  64. package/src/session-analytics/useSessionStart.ts +22 -2
  65. package/src/showcase/FeatureShowcase.tsx +50 -3
  66. package/src/types.ts +5 -4
  67. package/src/utils/withDeadline.ts +70 -0
@@ -81,33 +81,6 @@ type DeviceContext = {
81
81
  */
82
82
  declare const collectDeviceContext: () => DeviceContext;
83
83
 
84
- /**
85
- * reportClientEvent — forward DEVICE-ONLY onboarding events to the Wire AI analytics
86
- * backend (`POST {serverUrl}/v1/events`), completing the funnel for events the server
87
- * can't observe on its own.
88
- *
89
- * The backend already records the server-observable funnel during the A2A flow
90
- * (`session_started`, `screen_shown`, `answer_submitted`, `completed`, `llm_fallback`,
91
- * and even `screen_skipped` — it derives that from the kit's skip sentinel). The one
92
- * event no server request can capture is `dropped`: the user closing the app / unmounting
93
- * the flow without finishing. That's what this reporter is for.
94
- *
95
- * Contract (server: routers/onboarding.py → analytics/events.py):
96
- * POST {serverUrl}/v1/events
97
- * Authorization: Bearer {apiKey}
98
- * { "events": [ { event_type, session_id, screen_index?, component?, question_key?,
99
- * latency_ms?, meta?, device?, user_context? } ] }
100
- * The server fills `app_id` + `environment` from the resolving key (never send app_id),
101
- * and silently skips malformed events — one bad payload never fails the batch.
102
- *
103
- * ⚠️ Correlation: `session_id` MUST equal the A2A `contextId` the server uses to key the
104
- * server-side events, or the funnel report (which groups by `session_id`) treats this as a
105
- * phantom session. See `makeSessionId` + WireOnboarding for how the kit seeds it.
106
- *
107
- * Fire-and-forget: this never throws into the UI and never awaits — analytics must never
108
- * be able to break onboarding.
109
- */
110
-
111
84
  /** Event types a CLIENT may report. The rest of the funnel is server-side; sending those
112
85
  * here would double-count. `screen_skipped` is included for completeness, but the kit does
113
86
  * NOT emit it — the backend already derives it from the skip sentinel (see OnboardingFlow).
@@ -669,10 +642,11 @@ type WireOnboardingProps = {
669
642
  * Build the value with the helper so the wire spelling is decided in one place:
670
643
  *
671
644
  * ```tsx
672
- * <WireOnboarding userContext={activationJoinContext(deviceKey)} ... />
673
- * // no device id of your own? pass `storage` and leave this prop alone the kit injects its
674
- * // own key, and only after it has confirmed the key actually persists (see `autoJoinKey`).
675
- * <WireOnboarding config={{ ...config, storage }} ... />
645
+ * <WireOnboarding config={config} userContext={activationJoinContext(deviceKey)} ... />
646
+ * // no device id of your own? pass the `storage` PROP (it is a prop of this component, never a
647
+ * // field of `WireOnboardingConfig`) and leave this prop alone the kit injects its own key,
648
+ * // and only after it has confirmed the key actually persists (see `autoJoinKey`).
649
+ * <WireOnboarding config={config} storage={storage} ... />
676
650
  * ```
677
651
  *
678
652
  * ⛔ Do NOT hand-build the auto key with `activationJoinContext(resolveAutoDeviceKey({...}))`.
@@ -1402,8 +1376,19 @@ declare const resolveAutoDeviceKey: (opts?: ResolveAutoDeviceKeyOptions) => stri
1402
1376
  * follows it. Only a caller that can afford one storage read should use this; the fire-and-forget
1403
1377
  * event paths must stay on the sync function.
1404
1378
  *
1405
- * Never throws or rejects: a missing, hung, or rejecting adapter resolves to the in-memory id, and
1406
- * with no `storage` it resolves immediately (there is nothing to hydrate from).
1379
+ * Never throws or rejects: a missing or rejecting adapter resolves to the in-memory id, and with no
1380
+ * `storage` it resolves immediately (there is nothing to hydrate from).
1381
+ *
1382
+ * ⛔ A HUNG ADAPTER IS THE ONE CASE IT DOES NOT COVER, and this line used to claim it did. The read
1383
+ * underneath is a bare `storage.getItem` with no ceiling of its own, so an adapter that neither
1384
+ * resolves nor rejects (a locked keychain, a wedged native bridge) leaves THIS PROMISE PENDING
1385
+ * FOREVER — it does not fall back to the in-memory id, it simply never answers. Anything that gates
1386
+ * a user-visible or metric-bearing action on the result must therefore race it against a ceiling of
1387
+ * its own: `<WireOnboarding>` does (`AUTO_JOIN_HYDRATION_TIMEOUT_MS`), and so do the two lifecycle
1388
+ * hooks (`session-analytics/useLifecycleEvents` + `useSessionStart`, through the shared
1389
+ * `withTimeout` + `READ_TIMEOUT_MS` in `session/persistedSession`) — a hung read there had silently
1390
+ * killed `app.session_started` and `app.first_open` for the whole process. Use the SYNC
1391
+ * {@link resolveAutoDeviceKey} when you cannot afford to wait at all.
1407
1392
  *
1408
1393
  * ⚠️ IT RETURNS A BARE STRING, so it CANNOT say whether the id survives the launch — a degraded
1409
1394
  * adapter resolves to the in-memory mint and reads identically to a persisted one. No kit surface
@@ -1425,7 +1410,9 @@ declare const hydrateAutoDeviceKey: (opts?: ResolveAutoDeviceKeyOptions) => Prom
1425
1410
  *
1426
1411
  * Resolves `undefined` only when there is no usable id at all. With no `storage` it resolves
1427
1412
  * immediately with `durable: false` — a process-scoped id is exactly what "no persistence" means.
1428
- * Never throws or rejects.
1413
+ * Never throws or rejects — but, exactly like {@link hydrateAutoDeviceKey} above, it can also never
1414
+ * SETTLE on a hung adapter (the underlying `getItem` carries no ceiling), so every caller races it
1415
+ * against one of its own. See the note there.
1429
1416
  */
1430
1417
  declare const hydrateDeviceIdentity: (opts?: ResolveAutoDeviceKeyOptions) => Promise<IdentityRecord | undefined>;
1431
1418
  /** Test-only: forget every auto id + hydration flag so a unit test starts from a clean registry. */
@@ -1438,7 +1425,10 @@ declare const resetAutoDeviceKeys: () => void;
1438
1425
  declare const setCurrentSessionId: (id: string | undefined) => void;
1439
1426
  /** The current per-open `session_id`, or `undefined` when no app-open has been registered yet. */
1440
1427
  declare const getCurrentSessionId: () => string | undefined;
1441
- /** Test-only: forget the current session id so a unit test starts from a clean registry. */
1428
+ /** Test-only: forget the current session id so a unit test starts from a clean registry. Does NOT
1429
+ * clear the MINTED-id record (see `isMintedSessionId`), for the same reason the warn latch below is
1430
+ * not cleared: `makeSessionId()` never repeats, so a remembered fallback id can never again equal a
1431
+ * live one, and the record is overwritten by the next mint anyway. */
1442
1432
  declare const resetCurrentSessionId: () => void;
1443
1433
  /**
1444
1434
  * The current per-open `session_id`, MINTING and registering one when no app-open has been
@@ -81,33 +81,6 @@ type DeviceContext = {
81
81
  */
82
82
  declare const collectDeviceContext: () => DeviceContext;
83
83
 
84
- /**
85
- * reportClientEvent — forward DEVICE-ONLY onboarding events to the Wire AI analytics
86
- * backend (`POST {serverUrl}/v1/events`), completing the funnel for events the server
87
- * can't observe on its own.
88
- *
89
- * The backend already records the server-observable funnel during the A2A flow
90
- * (`session_started`, `screen_shown`, `answer_submitted`, `completed`, `llm_fallback`,
91
- * and even `screen_skipped` — it derives that from the kit's skip sentinel). The one
92
- * event no server request can capture is `dropped`: the user closing the app / unmounting
93
- * the flow without finishing. That's what this reporter is for.
94
- *
95
- * Contract (server: routers/onboarding.py → analytics/events.py):
96
- * POST {serverUrl}/v1/events
97
- * Authorization: Bearer {apiKey}
98
- * { "events": [ { event_type, session_id, screen_index?, component?, question_key?,
99
- * latency_ms?, meta?, device?, user_context? } ] }
100
- * The server fills `app_id` + `environment` from the resolving key (never send app_id),
101
- * and silently skips malformed events — one bad payload never fails the batch.
102
- *
103
- * ⚠️ Correlation: `session_id` MUST equal the A2A `contextId` the server uses to key the
104
- * server-side events, or the funnel report (which groups by `session_id`) treats this as a
105
- * phantom session. See `makeSessionId` + WireOnboarding for how the kit seeds it.
106
- *
107
- * Fire-and-forget: this never throws into the UI and never awaits — analytics must never
108
- * be able to break onboarding.
109
- */
110
-
111
84
  /** Event types a CLIENT may report. The rest of the funnel is server-side; sending those
112
85
  * here would double-count. `screen_skipped` is included for completeness, but the kit does
113
86
  * NOT emit it — the backend already derives it from the skip sentinel (see OnboardingFlow).
@@ -669,10 +642,11 @@ type WireOnboardingProps = {
669
642
  * Build the value with the helper so the wire spelling is decided in one place:
670
643
  *
671
644
  * ```tsx
672
- * <WireOnboarding userContext={activationJoinContext(deviceKey)} ... />
673
- * // no device id of your own? pass `storage` and leave this prop alone the kit injects its
674
- * // own key, and only after it has confirmed the key actually persists (see `autoJoinKey`).
675
- * <WireOnboarding config={{ ...config, storage }} ... />
645
+ * <WireOnboarding config={config} userContext={activationJoinContext(deviceKey)} ... />
646
+ * // no device id of your own? pass the `storage` PROP (it is a prop of this component, never a
647
+ * // field of `WireOnboardingConfig`) and leave this prop alone the kit injects its own key,
648
+ * // and only after it has confirmed the key actually persists (see `autoJoinKey`).
649
+ * <WireOnboarding config={config} storage={storage} ... />
676
650
  * ```
677
651
  *
678
652
  * ⛔ Do NOT hand-build the auto key with `activationJoinContext(resolveAutoDeviceKey({...}))`.
@@ -1402,8 +1376,19 @@ declare const resolveAutoDeviceKey: (opts?: ResolveAutoDeviceKeyOptions) => stri
1402
1376
  * follows it. Only a caller that can afford one storage read should use this; the fire-and-forget
1403
1377
  * event paths must stay on the sync function.
1404
1378
  *
1405
- * Never throws or rejects: a missing, hung, or rejecting adapter resolves to the in-memory id, and
1406
- * with no `storage` it resolves immediately (there is nothing to hydrate from).
1379
+ * Never throws or rejects: a missing or rejecting adapter resolves to the in-memory id, and with no
1380
+ * `storage` it resolves immediately (there is nothing to hydrate from).
1381
+ *
1382
+ * ⛔ A HUNG ADAPTER IS THE ONE CASE IT DOES NOT COVER, and this line used to claim it did. The read
1383
+ * underneath is a bare `storage.getItem` with no ceiling of its own, so an adapter that neither
1384
+ * resolves nor rejects (a locked keychain, a wedged native bridge) leaves THIS PROMISE PENDING
1385
+ * FOREVER — it does not fall back to the in-memory id, it simply never answers. Anything that gates
1386
+ * a user-visible or metric-bearing action on the result must therefore race it against a ceiling of
1387
+ * its own: `<WireOnboarding>` does (`AUTO_JOIN_HYDRATION_TIMEOUT_MS`), and so do the two lifecycle
1388
+ * hooks (`session-analytics/useLifecycleEvents` + `useSessionStart`, through the shared
1389
+ * `withTimeout` + `READ_TIMEOUT_MS` in `session/persistedSession`) — a hung read there had silently
1390
+ * killed `app.session_started` and `app.first_open` for the whole process. Use the SYNC
1391
+ * {@link resolveAutoDeviceKey} when you cannot afford to wait at all.
1407
1392
  *
1408
1393
  * ⚠️ IT RETURNS A BARE STRING, so it CANNOT say whether the id survives the launch — a degraded
1409
1394
  * adapter resolves to the in-memory mint and reads identically to a persisted one. No kit surface
@@ -1425,7 +1410,9 @@ declare const hydrateAutoDeviceKey: (opts?: ResolveAutoDeviceKeyOptions) => Prom
1425
1410
  *
1426
1411
  * Resolves `undefined` only when there is no usable id at all. With no `storage` it resolves
1427
1412
  * immediately with `durable: false` — a process-scoped id is exactly what "no persistence" means.
1428
- * Never throws or rejects.
1413
+ * Never throws or rejects — but, exactly like {@link hydrateAutoDeviceKey} above, it can also never
1414
+ * SETTLE on a hung adapter (the underlying `getItem` carries no ceiling), so every caller races it
1415
+ * against one of its own. See the note there.
1429
1416
  */
1430
1417
  declare const hydrateDeviceIdentity: (opts?: ResolveAutoDeviceKeyOptions) => Promise<IdentityRecord | undefined>;
1431
1418
  /** Test-only: forget every auto id + hydration flag so a unit test starts from a clean registry. */
@@ -1438,7 +1425,10 @@ declare const resetAutoDeviceKeys: () => void;
1438
1425
  declare const setCurrentSessionId: (id: string | undefined) => void;
1439
1426
  /** The current per-open `session_id`, or `undefined` when no app-open has been registered yet. */
1440
1427
  declare const getCurrentSessionId: () => string | undefined;
1441
- /** Test-only: forget the current session id so a unit test starts from a clean registry. */
1428
+ /** Test-only: forget the current session id so a unit test starts from a clean registry. Does NOT
1429
+ * clear the MINTED-id record (see `isMintedSessionId`), for the same reason the warn latch below is
1430
+ * not cleared: `makeSessionId()` never repeats, so a remembered fallback id can never again equal a
1431
+ * live one, and the record is overwritten by the next mint anyway. */
1442
1432
  declare const resetCurrentSessionId: () => void;
1443
1433
  /**
1444
1434
  * The current per-open `session_id`, MINTING and registering one when no app-open has been
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { ReactNode } from 'react';
3
- import { I as WireOnboardingProps, J as WireOnboardingConfig, O as OnboardingResult, K as WirePermissionStatus, P as PermissionStage, L as WirePermissionOutcome, S as StepValidator, M as OnboardingEvent, N as OnboardingCopy, C as ClientEventTarget, Q as DeviceContext, T as PermissionScreenConfig, W as WireUserContext, c as ClientEvent, U as PermissionPlacement, g as EnvelopeSource } from './currentSession-orZy5p1e.mjs';
4
- export { A as AUTO_DEVICE_ID_PREFIX, a as AnalyticsEvent, b as ClearUserContextOptions, d as ClientEventType, V as DEFAULT_PERMISSION_COPY, X as DeviceFormFactor, D as DeviceKeyStorage, Y as EXTRA_KEY_PREFIX, Z as GENERIC_PERMISSION_COPY, _ as IdentifyOnboardingBinding, $ as IdentifyOnboardingOptions, a0 as IdentityRecord, a1 as IdentitySource, a2 as IdentitySpace, a3 as NOTIFICATIONS_PERMISSION_COPY, a4 as OnboardingProgress, a5 as PermissionScreenCopy, R as ResolveAutoDeviceKeyOptions, a6 as ResolveUserContextOptions, a7 as ResolvedUserContext, a8 as USER_ID_MAX_LENGTH, i as WIRE_ONBOARDING_EVENTS, a9 as WIRE_PERMISSION_EVENTS, j as WireOnboardingEventName, aa as WirePermissionEventName, ab as WirePermissionKind, ac as activationJoinContext, k as analyticsUserIdStorageKey, m as clearPiiFromContext, n as clearUserContext, ad as collectDeviceContext, p as deviceIdStorageKey, q as ensureCurrentSessionId, r as getCurrentSessionId, ae as hashEmailFnv1a, af as hostIdentity, ag as hydrateAutoDeviceKey, ah as hydrateDeviceIdentity, ai as identifyOnboarding, aj as isWireScalar, s as looksLikeEmail, t as makeSessionId, ak as mintDeviceId, al as namespaceExtra, am as normalizePermissionStatus, an as permissionEventName, ao as permissionEventProps, u as reportClientEvent, v as reportClientEventAwait, w as reportClientEvents, x as reportClientEventsAwait, y as resetAutoDeviceKeys, z as resetCurrentSessionId, ap as resetIdentityProvenance, F as resolveAutoDeviceKey, aq as resolveIdentity, ar as resolvePermissionCopy, as as resolveUserContext, at as sanitizeUserId, G as setCurrentSessionId, H as toAnalyticsEvent } from './currentSession-orZy5p1e.mjs';
3
+ import { I as WireOnboardingProps, J as WireOnboardingConfig, O as OnboardingResult, K as WirePermissionStatus, P as PermissionStage, L as WirePermissionOutcome, S as StepValidator, M as OnboardingEvent, N as OnboardingCopy, C as ClientEventTarget, Q as DeviceContext, T as PermissionScreenConfig, W as WireUserContext, c as ClientEvent, U as PermissionPlacement, g as EnvelopeSource } from './currentSession-Bz7G6lno.mjs';
4
+ export { A as AUTO_DEVICE_ID_PREFIX, a as AnalyticsEvent, b as ClearUserContextOptions, d as ClientEventType, V as DEFAULT_PERMISSION_COPY, X as DeviceFormFactor, D as DeviceKeyStorage, Y as EXTRA_KEY_PREFIX, Z as GENERIC_PERMISSION_COPY, _ as IdentifyOnboardingBinding, $ as IdentifyOnboardingOptions, a0 as IdentityRecord, a1 as IdentitySource, a2 as IdentitySpace, a3 as NOTIFICATIONS_PERMISSION_COPY, a4 as OnboardingProgress, a5 as PermissionScreenCopy, R as ResolveAutoDeviceKeyOptions, a6 as ResolveUserContextOptions, a7 as ResolvedUserContext, a8 as USER_ID_MAX_LENGTH, i as WIRE_ONBOARDING_EVENTS, a9 as WIRE_PERMISSION_EVENTS, j as WireOnboardingEventName, aa as WirePermissionEventName, ab as WirePermissionKind, ac as activationJoinContext, k as analyticsUserIdStorageKey, m as clearPiiFromContext, n as clearUserContext, ad as collectDeviceContext, p as deviceIdStorageKey, q as ensureCurrentSessionId, r as getCurrentSessionId, ae as hashEmailFnv1a, af as hostIdentity, ag as hydrateAutoDeviceKey, ah as hydrateDeviceIdentity, ai as identifyOnboarding, aj as isWireScalar, s as looksLikeEmail, t as makeSessionId, ak as mintDeviceId, al as namespaceExtra, am as normalizePermissionStatus, an as permissionEventName, ao as permissionEventProps, u as reportClientEvent, v as reportClientEventAwait, w as reportClientEvents, x as reportClientEventsAwait, y as resetAutoDeviceKeys, z as resetCurrentSessionId, ap as resetIdentityProvenance, F as resolveAutoDeviceKey, aq as resolveIdentity, ar as resolvePermissionCopy, as as resolveUserContext, at as sanitizeUserId, G as setCurrentSessionId, H as toAnalyticsEvent } from './currentSession-Bz7G6lno.mjs';
5
5
  import { O as OnboardingTheme } from './types-BKfpdZzX.mjs';
6
6
  export { a as OnboardingButtonStyle, b as OnboardingColors, c as OnboardingFonts, d as OnboardingRadius, e as OnboardingSpacing } from './types-BKfpdZzX.mjs';
7
7
  export { C as CenteredModal, a as CenteredModalHandle, b as CenteredModalProps } from './CenteredModal-C3qQBHsA.mjs';
@@ -1301,6 +1301,7 @@ declare const readProgress: (response?: WireAIResponse) => PartialProgress;
1301
1301
  */
1302
1302
  declare const fetchWireFeatures: (config?: WireFeaturesConfig) => Promise<WireFeatures>;
1303
1303
 
1304
+ /** The flags alone — the original hook, unchanged for every consumer that cannot act on `settled`. */
1304
1305
  declare const useWireFeatures: (config?: WireFeaturesConfig) => WireFeatures;
1305
1306
 
1306
1307
  /**
@@ -2165,6 +2166,8 @@ interface ReportFirstOpenOptions extends LifecycleEventInput {
2165
2166
  * • Race guard: an in-memory latch is set SYNCHRONOUSLY before the async read, so two
2166
2167
  * near-simultaneous calls fire at most once.
2167
2168
  * • Without `storage`: degraded mode — fires once per PROCESS via the latch only (documented).
2169
+ * • WITHOUT A TRANSPORT: no-op, and — the point of the check — the once-ever flag is NOT spent.
2170
+ * See below.
2168
2171
  */
2169
2172
  declare const reportFirstOpen: (opts: ReportFirstOpenOptions) => void;
2170
2173
  /** Options for {@link wireLifecycleEvents}: the shared lifecycle inputs + first-open persistence. */
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { ReactNode } from 'react';
3
- import { I as WireOnboardingProps, J as WireOnboardingConfig, O as OnboardingResult, K as WirePermissionStatus, P as PermissionStage, L as WirePermissionOutcome, S as StepValidator, M as OnboardingEvent, N as OnboardingCopy, C as ClientEventTarget, Q as DeviceContext, T as PermissionScreenConfig, W as WireUserContext, c as ClientEvent, U as PermissionPlacement, g as EnvelopeSource } from './currentSession-CFSRZ2wg.js';
4
- export { A as AUTO_DEVICE_ID_PREFIX, a as AnalyticsEvent, b as ClearUserContextOptions, d as ClientEventType, V as DEFAULT_PERMISSION_COPY, X as DeviceFormFactor, D as DeviceKeyStorage, Y as EXTRA_KEY_PREFIX, Z as GENERIC_PERMISSION_COPY, _ as IdentifyOnboardingBinding, $ as IdentifyOnboardingOptions, a0 as IdentityRecord, a1 as IdentitySource, a2 as IdentitySpace, a3 as NOTIFICATIONS_PERMISSION_COPY, a4 as OnboardingProgress, a5 as PermissionScreenCopy, R as ResolveAutoDeviceKeyOptions, a6 as ResolveUserContextOptions, a7 as ResolvedUserContext, a8 as USER_ID_MAX_LENGTH, i as WIRE_ONBOARDING_EVENTS, a9 as WIRE_PERMISSION_EVENTS, j as WireOnboardingEventName, aa as WirePermissionEventName, ab as WirePermissionKind, ac as activationJoinContext, k as analyticsUserIdStorageKey, m as clearPiiFromContext, n as clearUserContext, ad as collectDeviceContext, p as deviceIdStorageKey, q as ensureCurrentSessionId, r as getCurrentSessionId, ae as hashEmailFnv1a, af as hostIdentity, ag as hydrateAutoDeviceKey, ah as hydrateDeviceIdentity, ai as identifyOnboarding, aj as isWireScalar, s as looksLikeEmail, t as makeSessionId, ak as mintDeviceId, al as namespaceExtra, am as normalizePermissionStatus, an as permissionEventName, ao as permissionEventProps, u as reportClientEvent, v as reportClientEventAwait, w as reportClientEvents, x as reportClientEventsAwait, y as resetAutoDeviceKeys, z as resetCurrentSessionId, ap as resetIdentityProvenance, F as resolveAutoDeviceKey, aq as resolveIdentity, ar as resolvePermissionCopy, as as resolveUserContext, at as sanitizeUserId, G as setCurrentSessionId, H as toAnalyticsEvent } from './currentSession-CFSRZ2wg.js';
3
+ import { I as WireOnboardingProps, J as WireOnboardingConfig, O as OnboardingResult, K as WirePermissionStatus, P as PermissionStage, L as WirePermissionOutcome, S as StepValidator, M as OnboardingEvent, N as OnboardingCopy, C as ClientEventTarget, Q as DeviceContext, T as PermissionScreenConfig, W as WireUserContext, c as ClientEvent, U as PermissionPlacement, g as EnvelopeSource } from './currentSession-z-CZ55ad.js';
4
+ export { A as AUTO_DEVICE_ID_PREFIX, a as AnalyticsEvent, b as ClearUserContextOptions, d as ClientEventType, V as DEFAULT_PERMISSION_COPY, X as DeviceFormFactor, D as DeviceKeyStorage, Y as EXTRA_KEY_PREFIX, Z as GENERIC_PERMISSION_COPY, _ as IdentifyOnboardingBinding, $ as IdentifyOnboardingOptions, a0 as IdentityRecord, a1 as IdentitySource, a2 as IdentitySpace, a3 as NOTIFICATIONS_PERMISSION_COPY, a4 as OnboardingProgress, a5 as PermissionScreenCopy, R as ResolveAutoDeviceKeyOptions, a6 as ResolveUserContextOptions, a7 as ResolvedUserContext, a8 as USER_ID_MAX_LENGTH, i as WIRE_ONBOARDING_EVENTS, a9 as WIRE_PERMISSION_EVENTS, j as WireOnboardingEventName, aa as WirePermissionEventName, ab as WirePermissionKind, ac as activationJoinContext, k as analyticsUserIdStorageKey, m as clearPiiFromContext, n as clearUserContext, ad as collectDeviceContext, p as deviceIdStorageKey, q as ensureCurrentSessionId, r as getCurrentSessionId, ae as hashEmailFnv1a, af as hostIdentity, ag as hydrateAutoDeviceKey, ah as hydrateDeviceIdentity, ai as identifyOnboarding, aj as isWireScalar, s as looksLikeEmail, t as makeSessionId, ak as mintDeviceId, al as namespaceExtra, am as normalizePermissionStatus, an as permissionEventName, ao as permissionEventProps, u as reportClientEvent, v as reportClientEventAwait, w as reportClientEvents, x as reportClientEventsAwait, y as resetAutoDeviceKeys, z as resetCurrentSessionId, ap as resetIdentityProvenance, F as resolveAutoDeviceKey, aq as resolveIdentity, ar as resolvePermissionCopy, as as resolveUserContext, at as sanitizeUserId, G as setCurrentSessionId, H as toAnalyticsEvent } from './currentSession-z-CZ55ad.js';
5
5
  import { O as OnboardingTheme } from './types-BKfpdZzX.js';
6
6
  export { a as OnboardingButtonStyle, b as OnboardingColors, c as OnboardingFonts, d as OnboardingRadius, e as OnboardingSpacing } from './types-BKfpdZzX.js';
7
7
  export { C as CenteredModal, a as CenteredModalHandle, b as CenteredModalProps } from './CenteredModal-Cdgns6--.js';
@@ -1301,6 +1301,7 @@ declare const readProgress: (response?: WireAIResponse) => PartialProgress;
1301
1301
  */
1302
1302
  declare const fetchWireFeatures: (config?: WireFeaturesConfig) => Promise<WireFeatures>;
1303
1303
 
1304
+ /** The flags alone — the original hook, unchanged for every consumer that cannot act on `settled`. */
1304
1305
  declare const useWireFeatures: (config?: WireFeaturesConfig) => WireFeatures;
1305
1306
 
1306
1307
  /**
@@ -2165,6 +2166,8 @@ interface ReportFirstOpenOptions extends LifecycleEventInput {
2165
2166
  * • Race guard: an in-memory latch is set SYNCHRONOUSLY before the async read, so two
2166
2167
  * near-simultaneous calls fire at most once.
2167
2168
  * • Without `storage`: degraded mode — fires once per PROCESS via the latch only (documented).
2169
+ * • WITHOUT A TRANSPORT: no-op, and — the point of the check — the once-ever flag is NOT spent.
2170
+ * See below.
2168
2171
  */
2169
2172
  declare const reportFirstOpen: (opts: ReportFirstOpenOptions) => void;
2170
2173
  /** Options for {@link wireLifecycleEvents}: the shared lifecycle inputs + first-open persistence. */
package/dist/index.js CHANGED
@@ -1168,6 +1168,28 @@ var readProgress = (response) => {
1168
1168
  };
1169
1169
  };
1170
1170
 
1171
+ // src/utils/withDeadline.ts
1172
+ var DEADLINE_EXPIRED = /* @__PURE__ */ Symbol("wire-deadline-expired");
1173
+ var DEFAULT_DEADLINE_MS = 15e3;
1174
+ var withDeadline = async (exchange, timeoutMs = DEFAULT_DEADLINE_MS) => {
1175
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
1176
+ let timer;
1177
+ const expired = new Promise((resolve) => {
1178
+ timer = setTimeout(() => {
1179
+ controller == null ? void 0 : controller.abort();
1180
+ resolve(DEADLINE_EXPIRED);
1181
+ }, timeoutMs);
1182
+ });
1183
+ const running = exchange(controller == null ? void 0 : controller.signal);
1184
+ try {
1185
+ return await Promise.race([running, expired]);
1186
+ } finally {
1187
+ clearTimeout(timer);
1188
+ void running.catch(() => {
1189
+ });
1190
+ }
1191
+ };
1192
+
1171
1193
  // src/analytics/reportClientEvent.ts
1172
1194
  var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
1173
1195
  var toWireEvents = (events) => events.map((event) => {
@@ -1239,14 +1261,17 @@ var reportClientEventsOutcome = async (target, events) => {
1239
1261
  try {
1240
1262
  const req = buildEventsRequest(target, events);
1241
1263
  if (!req) return "refused";
1242
- const res = await fetch(req.url, req.init);
1243
- if (!res || !res.ok) return "unreachable";
1244
- const ack = await readEventsAck(res);
1245
- if (!ack || ack.skipped <= 0) return "delivered";
1246
- if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
1247
- console.warn(describeDiscarded(ack));
1248
- }
1249
- return "refused";
1264
+ const outcome = await withDeadline(async (signal) => {
1265
+ const res = await fetch(req.url, { ...req.init, signal });
1266
+ if (!res || !res.ok) return "unreachable";
1267
+ const ack = await readEventsAck(res);
1268
+ if (!ack || ack.skipped <= 0) return "delivered";
1269
+ if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
1270
+ console.warn(describeDiscarded(ack));
1271
+ }
1272
+ return "refused";
1273
+ });
1274
+ return outcome === DEADLINE_EXPIRED ? "unreachable" : outcome;
1250
1275
  } catch {
1251
1276
  return "unreachable";
1252
1277
  }
@@ -3478,11 +3503,11 @@ var provenanceRegistry = () => {
3478
3503
  return created;
3479
3504
  };
3480
3505
  var provenanceKey = (space, scope) => `${space}:${scope != null ? scope : "default"}`;
3506
+ var isUsableIdentityValue = (value) => typeof value === "string" && value.trim().length > 0;
3481
3507
  var resolveIdentity = (input) => {
3482
3508
  var _a;
3483
- if (typeof input.value !== "string") return void 0;
3509
+ if (!isUsableIdentityValue(input.value)) return void 0;
3484
3510
  const value = input.value.trim();
3485
- if (!value) return void 0;
3486
3511
  const durable = (_a = input.durable) != null ? _a : input.source === "host";
3487
3512
  if (input.source === "host") {
3488
3513
  provenanceRegistry().host.set(provenanceKey(input.space, input.scope), value);
@@ -3587,6 +3612,7 @@ var resetAutoDeviceKeys = () => {
3587
3612
  registry.keys.clear();
3588
3613
  registry.hydrating.clear();
3589
3614
  (_a = registry.pending) == null ? void 0 : _a.clear();
3615
+ registry.ambient = void 0;
3590
3616
  };
3591
3617
 
3592
3618
  // src/analytics/currentSession.ts
@@ -3594,25 +3620,51 @@ var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
3594
3620
  "@wireai/activation:currentSessionId"
3595
3621
  );
3596
3622
  var globalSlot = globalThis;
3623
+ var SESSION_ID_LISTENERS_SLOT = /* @__PURE__ */ Symbol.for(
3624
+ "@wireai/activation:currentSessionIdListeners"
3625
+ );
3626
+ var listenerSlot = globalThis;
3627
+ var listeners = () => {
3628
+ const existing = listenerSlot[SESSION_ID_LISTENERS_SLOT];
3629
+ if (existing) return existing;
3630
+ const created = /* @__PURE__ */ new Set();
3631
+ listenerSlot[SESSION_ID_LISTENERS_SLOT] = created;
3632
+ return created;
3633
+ };
3634
+ var writeCurrentSessionId = (id) => {
3635
+ if (globalSlot[CURRENT_SESSION_ID_SLOT] === id) return;
3636
+ globalSlot[CURRENT_SESSION_ID_SLOT] = id;
3637
+ for (const listener of Array.from(listeners())) {
3638
+ try {
3639
+ listener();
3640
+ } catch {
3641
+ }
3642
+ }
3643
+ };
3597
3644
  var setCurrentSessionId = (id) => {
3598
3645
  if (typeof id === "string" && id.length > 0) {
3599
- globalSlot[CURRENT_SESSION_ID_SLOT] = id;
3646
+ writeCurrentSessionId(id);
3600
3647
  }
3601
3648
  };
3602
3649
  var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
3603
3650
  var resetCurrentSessionId = () => {
3604
- globalSlot[CURRENT_SESSION_ID_SLOT] = void 0;
3651
+ writeCurrentSessionId(void 0);
3605
3652
  };
3606
3653
  var MINT_WARNING = "[wireai] No app-open session was registered, so a session id was minted for this event (the server drops an event that has no session_id, and still answers 200). Mount useLifecycleEvents at your app root so events correlate to a real app-open.";
3607
3654
  var MINT_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
3608
3655
  "@wireai/activation:currentSessionIdMintWarned"
3609
3656
  );
3610
3657
  var warnSlot = globalThis;
3658
+ var MINTED_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
3659
+ "@wireai/activation:mintedSessionId"
3660
+ );
3661
+ var mintedSlot = globalThis;
3611
3662
  var ensureCurrentSessionId = () => {
3612
3663
  const existing = globalSlot[CURRENT_SESSION_ID_SLOT];
3613
3664
  if (typeof existing === "string" && existing.length > 0) return existing;
3614
3665
  const minted = makeSessionId();
3615
- globalSlot[CURRENT_SESSION_ID_SLOT] = minted;
3666
+ writeCurrentSessionId(minted);
3667
+ mintedSlot[MINTED_SESSION_ID_SLOT] = minted;
3616
3668
  if (!warnSlot[MINT_WARNED_SLOT] && warnInDev(MINT_WARNING)) {
3617
3669
  warnSlot[MINT_WARNED_SLOT] = true;
3618
3670
  }
@@ -4452,17 +4504,31 @@ var failOpen = async (storage, key) => {
4452
4504
  const cached2 = await readCachedFeatures(storage, key);
4453
4505
  return (_a = cached2 == null ? void 0 : cached2.features) != null ? _a : defaultWireFeatures;
4454
4506
  };
4455
- var fetchWithTimeout = async (url, apiKey, timeoutMs) => {
4507
+ var NO_ANSWER = /* @__PURE__ */ Symbol("wire-features-no-answer");
4508
+ var fetchFeaturesJson = async (url, apiKey, timeoutMs) => {
4456
4509
  const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
4457
- const timer = setTimeout(() => controller == null ? void 0 : controller.abort(), timeoutMs);
4458
- try {
4459
- return await fetch(url, {
4510
+ let timer;
4511
+ const expired = new Promise((resolve) => {
4512
+ timer = setTimeout(() => {
4513
+ controller == null ? void 0 : controller.abort();
4514
+ resolve(NO_ANSWER);
4515
+ }, timeoutMs);
4516
+ });
4517
+ const exchange = (async () => {
4518
+ const res = await fetch(url, {
4460
4519
  method: "GET",
4461
4520
  headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
4462
4521
  signal: controller == null ? void 0 : controller.signal
4463
4522
  });
4523
+ if (!res || !res.ok) return NO_ANSWER;
4524
+ return await res.json();
4525
+ })();
4526
+ try {
4527
+ return await Promise.race([exchange, expired]);
4464
4528
  } finally {
4465
4529
  clearTimeout(timer);
4530
+ void exchange.catch(() => {
4531
+ });
4466
4532
  }
4467
4533
  };
4468
4534
  var fetchWireFeatures = async (config) => {
@@ -4472,9 +4538,8 @@ var fetchWireFeatures = async (config) => {
4472
4538
  const url = `${config.serverUrl.replace(/\/$/, "")}/v1/features`;
4473
4539
  const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
4474
4540
  try {
4475
- const res = await fetchWithTimeout(url, config.apiKey, timeoutMs);
4476
- if (!res || !res.ok) return failOpen(storage, key);
4477
- const json = await res.json();
4541
+ const json = await fetchFeaturesJson(url, config.apiKey, timeoutMs);
4542
+ if (json === NO_ANSWER) return failOpen(storage, key);
4478
4543
  const features = parseWireFeatures(json);
4479
4544
  if (storage) writeCachedFeatures(storage, key, features);
4480
4545
  return features;
@@ -4488,50 +4553,65 @@ var useStableValue = (value, isEqual) => {
4488
4553
  return ref.current;
4489
4554
  };
4490
4555
  var sameFetchKey = (a, b) => (a == null ? void 0 : a.serverUrl) === (b == null ? void 0 : b.serverUrl) && (a == null ? void 0 : a.apiKey) === (b == null ? void 0 : b.apiKey) && (a == null ? void 0 : a.appId) === (b == null ? void 0 : b.appId);
4491
- var useWireFeatures = (config) => {
4556
+ var useWireFeaturesState = (config) => {
4492
4557
  const stableConfig = useStableValue(config, sameFetchKey);
4493
4558
  const [flags, setFlags] = React19.useState(defaultWireFeatures);
4494
4559
  const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
4560
+ const [settled, setSettled] = React19.useState(!canFetch);
4495
4561
  React19.useEffect(() => {
4496
4562
  if (!canFetch) {
4497
4563
  setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
4564
+ setSettled(true);
4498
4565
  return;
4499
4566
  }
4500
4567
  let alive = true;
4501
4568
  void fetchWireFeatures(stableConfig).then((next) => {
4502
4569
  if (!alive) return;
4503
4570
  setFlags((prev) => featuresEqual(prev, next) ? prev : next);
4571
+ setSettled(true);
4504
4572
  });
4505
4573
  return () => {
4506
4574
  alive = false;
4507
4575
  };
4508
4576
  }, [canFetch, stableConfig]);
4509
- return flags;
4577
+ return React19.useMemo(() => ({ flags, settled }), [flags, settled]);
4510
4578
  };
4579
+ var useWireFeatures = (config) => useWireFeaturesState(config).flags;
4511
4580
  var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
4512
- var globalObj = globalThis;
4513
- if (!globalObj[CONTEXT_SYMBOL]) {
4514
- globalObj[CONTEXT_SYMBOL] = React19.createContext(null);
4581
+ var featuresContextGlobal = globalThis;
4582
+ if (!featuresContextGlobal[CONTEXT_SYMBOL]) {
4583
+ featuresContextGlobal[CONTEXT_SYMBOL] = React19.createContext(null);
4515
4584
  }
4516
- var WireFeaturesContext = globalObj[CONTEXT_SYMBOL];
4585
+ var WireFeaturesContext = featuresContextGlobal[CONTEXT_SYMBOL];
4586
+ var SETTLED_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.settled.context");
4587
+ var settledContextGlobal = globalThis;
4588
+ if (!settledContextGlobal[SETTLED_SYMBOL]) {
4589
+ settledContextGlobal[SETTLED_SYMBOL] = React19.createContext(true);
4590
+ }
4591
+ var WireFeaturesSettledContext = settledContextGlobal[SETTLED_SYMBOL];
4517
4592
  var WireFeaturesProvider = ({
4518
4593
  config,
4519
4594
  flags,
4520
4595
  children
4521
4596
  }) => {
4522
- const fetched = useWireFeatures(flags ? void 0 : config);
4523
- const value = flags != null ? flags : fetched;
4597
+ const fetched = useWireFeaturesState(flags ? void 0 : config);
4598
+ const value = flags != null ? flags : fetched.flags;
4599
+ const settled = flags ? true : fetched.settled;
4524
4600
  const stable = React19.useMemo(() => value, [value]);
4525
- return /* @__PURE__ */ jsxRuntime.jsx(WireFeaturesContext.Provider, { value: stable, children });
4601
+ return /* @__PURE__ */ jsxRuntime.jsx(WireFeaturesSettledContext.Provider, { value: settled, children: /* @__PURE__ */ jsxRuntime.jsx(WireFeaturesContext.Provider, { value: stable, children }) });
4526
4602
  };
4527
4603
  WireFeaturesProvider.displayName = "WireFeaturesProvider";
4528
4604
  var useWireFeaturesContext = () => React19.useContext(WireFeaturesContext);
4529
- var useResolvedFeatures = (options) => {
4605
+ var useResolvedFeatures = (options) => useResolvedFeaturesState(options).flags;
4606
+ var useResolvedFeaturesState = (options) => {
4530
4607
  var _a, _b, _c;
4531
4608
  const ctx = useWireFeaturesContext();
4609
+ const ctxSettled = React19.useContext(WireFeaturesSettledContext);
4532
4610
  const shouldFetch = !(options == null ? void 0 : options.flags) && ctx == null;
4533
- const fetched = useWireFeatures(shouldFetch ? options == null ? void 0 : options.config : void 0);
4534
- return (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched) != null ? _c : defaultWireFeatures;
4611
+ const fetched = useWireFeaturesState(shouldFetch ? options == null ? void 0 : options.config : void 0);
4612
+ const flags = (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched.flags) != null ? _c : defaultWireFeatures;
4613
+ const settled = (options == null ? void 0 : options.flags) ? true : ctx != null ? ctxSettled : fetched.settled;
4614
+ return React19.useMemo(() => ({ flags, settled }), [flags, settled]);
4535
4615
  };
4536
4616
 
4537
4617
  // src/config/wireConfigFromEnv.ts
@@ -5275,8 +5355,12 @@ var useSessionStart = (config, options = {}) => {
5275
5355
  fireOpen(void 0);
5276
5356
  return;
5277
5357
  }
5278
- void hydrateDeviceIdentity({ appId: cfg.appId, storage: cfg.storage }).then((identity) => {
5358
+ void withTimeout(
5359
+ hydrateDeviceIdentity({ appId: cfg.appId, storage: cfg.storage }),
5360
+ READ_TIMEOUT_MS
5361
+ ).then((read) => {
5279
5362
  if (cancelled) return;
5363
+ const identity = read === READ_TIMED_OUT ? void 0 : read;
5280
5364
  fireOpen((identity == null ? void 0 : identity.durable) ? identity.value : void 0);
5281
5365
  });
5282
5366
  };
@@ -5376,8 +5460,9 @@ var emitFirstOpen = (opts) => {
5376
5460
  routeLifecycleEvent(buildLifecycleEvent(FIRST_OPEN_EVENT, opts), opts);
5377
5461
  };
5378
5462
  var reportFirstOpen = (opts) => {
5379
- var _a;
5380
- const appId = (_a = opts.appId) != null ? _a : "default";
5463
+ var _a, _b;
5464
+ if (!opts.sink && !((_a = opts.target) == null ? void 0 : _a.serverUrl)) return;
5465
+ const appId = (_b = opts.appId) != null ? _b : "default";
5381
5466
  if (_firstOpenLatched.has(appId)) return;
5382
5467
  _firstOpenLatched.add(appId);
5383
5468
  const storage = opts.storage;
@@ -5462,8 +5547,12 @@ var useLifecycleEvents = (config, options = {}) => {
5462
5547
  fire(void 0);
5463
5548
  return;
5464
5549
  }
5465
- void hydrateDeviceIdentity({ appId: cfg.appId, storage: cfg.storage }).then((identity) => {
5550
+ void withTimeout(
5551
+ hydrateDeviceIdentity({ appId: cfg.appId, storage: cfg.storage }),
5552
+ READ_TIMEOUT_MS
5553
+ ).then((read) => {
5466
5554
  if (cancelled) return;
5555
+ const identity = read === READ_TIMED_OUT ? void 0 : read;
5467
5556
  fire((identity == null ? void 0 : identity.durable) ? identity.value : void 0);
5468
5557
  });
5469
5558
  };