@wireai/activation 0.14.3 → 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 (85) hide show
  1. package/AGENTS.md +96 -21
  2. package/CHANGELOG.md +780 -0
  3. package/INTEGRATION_PROMPT.md +61 -23
  4. package/README.md +110 -31
  5. package/dist/analytics/index.d.mts +35 -13
  6. package/dist/analytics/index.d.ts +35 -13
  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 +15 -1
  12. package/dist/coachmarks/index.d.ts +15 -1
  13. package/dist/coachmarks/index.js +120 -22
  14. package/dist/coachmarks/index.js.map +1 -1
  15. package/dist/coachmarks/index.mjs +120 -22
  16. package/dist/coachmarks/index.mjs.map +1 -1
  17. package/dist/{currentSession-CUvTOchb.d.mts → currentSession-Bz7G6lno.d.mts} +35 -39
  18. package/dist/{currentSession-CW_5Mq4O.d.ts → currentSession-z-CZ55ad.d.ts} +35 -39
  19. package/dist/{decision-Bgo17oH7.d.mts → decision-3vWLuBlO.d.ts} +11 -2
  20. package/dist/{decision-Bkh_LigV.d.ts → decision-yBj2AyPW.d.mts} +11 -2
  21. package/dist/index.d.mts +5 -2
  22. package/dist/index.d.ts +5 -2
  23. package/dist/index.js +125 -36
  24. package/dist/index.js.map +1 -1
  25. package/dist/index.mjs +125 -36
  26. package/dist/index.mjs.map +1 -1
  27. package/dist/questionnaire/index.d.mts +3 -16
  28. package/dist/questionnaire/index.d.ts +3 -16
  29. package/dist/questionnaire/index.js +202 -46
  30. package/dist/questionnaire/index.js.map +1 -1
  31. package/dist/questionnaire/index.mjs +203 -47
  32. package/dist/questionnaire/index.mjs.map +1 -1
  33. package/dist/reviews/index.d.mts +10 -6
  34. package/dist/reviews/index.d.ts +10 -6
  35. package/dist/reviews/index.js +269 -52
  36. package/dist/reviews/index.js.map +1 -1
  37. package/dist/reviews/index.mjs +270 -53
  38. package/dist/reviews/index.mjs.map +1 -1
  39. package/dist/showcase/index.d.mts +1 -1
  40. package/dist/showcase/index.d.ts +1 -1
  41. package/dist/showcase/index.js +106 -20
  42. package/dist/showcase/index.js.map +1 -1
  43. package/dist/showcase/index.mjs +107 -21
  44. package/dist/showcase/index.mjs.map +1 -1
  45. package/dist/{transport-j5gFfJhK.d.mts → transport-s5QxA-ci.d.mts} +18 -13
  46. package/dist/{transport-B_0SgCBe.d.ts → transport-xqqSFqxs.d.ts} +18 -13
  47. package/dist/{types-Cju-1_jT.d.mts → types-Byx306Kv.d.mts} +25 -10
  48. package/dist/{types-BcmagF6K.d.mts → types-D_0B0yay.d.mts} +7 -2
  49. package/dist/{types-BcmagF6K.d.ts → types-D_0B0yay.d.ts} +7 -2
  50. package/dist/{types-h2BZvl1t.d.ts → types-tdATL5z0.d.ts} +25 -10
  51. package/llms.txt +9 -9
  52. package/package.json +6 -9
  53. package/src/WireOnboarding.tsx +4 -1
  54. package/src/analytics/currentSession.ts +141 -4
  55. package/src/analytics/index.ts +6 -1
  56. package/src/analytics/reportClientEvent.ts +19 -10
  57. package/src/analytics/useAnalytics.ts +74 -15
  58. package/src/analytics/wireDoctor.ts +152 -7
  59. package/src/coachmarks/CoachmarkProvider.tsx +26 -5
  60. package/src/coachmarks/runtime.ts +156 -2
  61. package/src/coachmarks/types.ts +7 -2
  62. package/src/coachmarks/useCoachmarkTour.ts +51 -1
  63. package/src/context/deviceId.ts +72 -6
  64. package/src/features/WireFeaturesProvider.tsx +72 -12
  65. package/src/features/fetchWireFeatures.ts +49 -11
  66. package/src/features/useWireFeatures.ts +39 -3
  67. package/src/identity/identityRecord.ts +15 -2
  68. package/src/questionnaire/QuestionnaireGate.tsx +40 -1
  69. package/src/questionnaire/transport.ts +22 -8
  70. package/src/questionnaire/useQuestionnaireGate.ts +58 -7
  71. package/src/reviews/ReviewGate.tsx +125 -37
  72. package/src/reviews/decision.ts +11 -1
  73. package/src/reviews/idempotency.ts +109 -0
  74. package/src/reviews/index.ts +4 -2
  75. package/src/reviews/runtime.ts +44 -13
  76. package/src/reviews/transport.ts +39 -20
  77. package/src/reviews/types.ts +7 -0
  78. package/src/reviews/useReviewGate.ts +57 -7
  79. package/src/session-analytics/lifecycle.ts +16 -0
  80. package/src/session-analytics/useLifecycleEvents.ts +30 -2
  81. package/src/session-analytics/useSessionStart.ts +22 -2
  82. package/src/showcase/FeatureShowcase.tsx +50 -3
  83. package/src/types.ts +15 -8
  84. package/src/utils/submitResult.ts +17 -9
  85. package/src/utils/withDeadline.ts +70 -0
@@ -8,6 +8,28 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
8
8
  throw Error('Dynamic require of "' + x + '" is not supported');
9
9
  });
10
10
 
11
+ // src/utils/withDeadline.ts
12
+ var DEADLINE_EXPIRED = /* @__PURE__ */ Symbol("wire-deadline-expired");
13
+ var DEFAULT_DEADLINE_MS = 15e3;
14
+ var withDeadline = async (exchange, timeoutMs = DEFAULT_DEADLINE_MS) => {
15
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
16
+ let timer;
17
+ const expired = new Promise((resolve) => {
18
+ timer = setTimeout(() => {
19
+ controller == null ? void 0 : controller.abort();
20
+ resolve(DEADLINE_EXPIRED);
21
+ }, timeoutMs);
22
+ });
23
+ const running = exchange(controller == null ? void 0 : controller.signal);
24
+ try {
25
+ return await Promise.race([running, expired]);
26
+ } finally {
27
+ clearTimeout(timer);
28
+ void running.catch(() => {
29
+ });
30
+ }
31
+ };
32
+
11
33
  // src/analytics/reportClientEvent.ts
12
34
  var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
13
35
  var toWireEvents = (events) => events.map((event) => {
@@ -79,14 +101,17 @@ var reportClientEventsOutcome = async (target, events) => {
79
101
  try {
80
102
  const req = buildEventsRequest(target, events);
81
103
  if (!req) return "refused";
82
- const res = await fetch(req.url, req.init);
83
- if (!res || !res.ok) return "unreachable";
84
- const ack = await readEventsAck(res);
85
- if (!ack || ack.skipped <= 0) return "delivered";
86
- if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
87
- console.warn(describeDiscarded(ack));
88
- }
89
- return "refused";
104
+ const outcome = await withDeadline(async (signal) => {
105
+ const res = await fetch(req.url, { ...req.init, signal });
106
+ if (!res || !res.ok) return "unreachable";
107
+ const ack = await readEventsAck(res);
108
+ if (!ack || ack.skipped <= 0) return "delivered";
109
+ if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
110
+ console.warn(describeDiscarded(ack));
111
+ }
112
+ return "refused";
113
+ });
114
+ return outcome === DEADLINE_EXPIRED ? "unreachable" : outcome;
90
115
  } catch {
91
116
  return "unreachable";
92
117
  }
@@ -107,25 +132,51 @@ var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
107
132
  "@wireai/activation:currentSessionId"
108
133
  );
109
134
  var globalSlot = globalThis;
135
+ var SESSION_ID_LISTENERS_SLOT = /* @__PURE__ */ Symbol.for(
136
+ "@wireai/activation:currentSessionIdListeners"
137
+ );
138
+ var listenerSlot = globalThis;
139
+ var listeners = () => {
140
+ const existing = listenerSlot[SESSION_ID_LISTENERS_SLOT];
141
+ if (existing) return existing;
142
+ const created = /* @__PURE__ */ new Set();
143
+ listenerSlot[SESSION_ID_LISTENERS_SLOT] = created;
144
+ return created;
145
+ };
146
+ var writeCurrentSessionId = (id) => {
147
+ if (globalSlot[CURRENT_SESSION_ID_SLOT] === id) return;
148
+ globalSlot[CURRENT_SESSION_ID_SLOT] = id;
149
+ for (const listener of Array.from(listeners())) {
150
+ try {
151
+ listener();
152
+ } catch {
153
+ }
154
+ }
155
+ };
110
156
  var setCurrentSessionId = (id) => {
111
157
  if (typeof id === "string" && id.length > 0) {
112
- globalSlot[CURRENT_SESSION_ID_SLOT] = id;
158
+ writeCurrentSessionId(id);
113
159
  }
114
160
  };
115
161
  var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
116
162
  var resetCurrentSessionId = () => {
117
- globalSlot[CURRENT_SESSION_ID_SLOT] = void 0;
163
+ writeCurrentSessionId(void 0);
118
164
  };
119
165
  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.";
120
166
  var MINT_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
121
167
  "@wireai/activation:currentSessionIdMintWarned"
122
168
  );
123
169
  var warnSlot = globalThis;
170
+ var MINTED_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
171
+ "@wireai/activation:mintedSessionId"
172
+ );
173
+ var mintedSlot = globalThis;
124
174
  var ensureCurrentSessionId = () => {
125
175
  const existing = globalSlot[CURRENT_SESSION_ID_SLOT];
126
176
  if (typeof existing === "string" && existing.length > 0) return existing;
127
177
  const minted = makeSessionId();
128
- globalSlot[CURRENT_SESSION_ID_SLOT] = minted;
178
+ writeCurrentSessionId(minted);
179
+ mintedSlot[MINTED_SESSION_ID_SLOT] = minted;
129
180
  if (!warnSlot[MINT_WARNED_SLOT] && warnInDev(MINT_WARNING)) {
130
181
  warnSlot[MINT_WARNED_SLOT] = true;
131
182
  }
@@ -226,6 +277,109 @@ var useScreenTracking = (navigationRef, options = {}) => {
226
277
  }, [navigationRef]);
227
278
  };
228
279
 
280
+ // src/identity/identityRecord.ts
281
+ var IDENTITY_PROVENANCE_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:identityProvenance");
282
+ var provenanceGlobal = globalThis;
283
+ var provenanceRegistry = () => {
284
+ const existing = provenanceGlobal[IDENTITY_PROVENANCE_SLOT];
285
+ if (existing) return existing;
286
+ const created = { host: /* @__PURE__ */ new Map() };
287
+ provenanceGlobal[IDENTITY_PROVENANCE_SLOT] = created;
288
+ return created;
289
+ };
290
+ var provenanceKey = (space, scope) => `${space}:${scope != null ? scope : "default"}`;
291
+ var isUsableIdentityValue = (value) => typeof value === "string" && value.trim().length > 0;
292
+ var resolveIdentity = (input) => {
293
+ var _a;
294
+ if (!isUsableIdentityValue(input.value)) return void 0;
295
+ const value = input.value.trim();
296
+ const durable = (_a = input.durable) != null ? _a : input.source === "host";
297
+ {
298
+ provenanceRegistry().host.set(provenanceKey(input.space, input.scope), value);
299
+ }
300
+ return { value, space: input.space, source: input.source, durable };
301
+ };
302
+
303
+ // src/context/deviceId.ts
304
+ var AUTO_DEVICE_ID_PREFIX = "wdev_";
305
+ var deviceIdStorageKey = (appId) => `wireai:analytics:deviceKey:${appId != null ? appId : "default"}`;
306
+ var randomChunk = () => Math.floor(Math.random() * 4294967296).toString(36).padStart(6, "0");
307
+ var mintDeviceId = () => {
308
+ const time = Date.now().toString(36);
309
+ return `${AUTO_DEVICE_ID_PREFIX}${time}_${randomChunk()}${randomChunk()}`;
310
+ };
311
+ var AUTO_DEVICE_KEY_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:autoDeviceKeys");
312
+ var deviceKeyGlobal = globalThis;
313
+ var autoDeviceKeyRegistry = () => {
314
+ const existing = deviceKeyGlobal[AUTO_DEVICE_KEY_SLOT];
315
+ if (existing) return existing;
316
+ const created = { keys: /* @__PURE__ */ new Map(), hydrating: /* @__PURE__ */ new Set() };
317
+ deviceKeyGlobal[AUTO_DEVICE_KEY_SLOT] = created;
318
+ return created;
319
+ };
320
+ var startHydration = (registry, appId, storage, minted) => {
321
+ if (!registry.pending) registry.pending = /* @__PURE__ */ new Map();
322
+ const existing = registry.pending.get(appId);
323
+ if (existing) return existing;
324
+ const slot = deviceIdStorageKey(appId);
325
+ const degraded = () => {
326
+ var _a;
327
+ return { value: (_a = registry.keys.get(appId)) != null ? _a : minted, durable: false };
328
+ };
329
+ const adopted = (value) => ({ value, durable: true });
330
+ let run;
331
+ try {
332
+ run = Promise.resolve(storage.getItem(slot)).then((saved) => {
333
+ const persisted = typeof saved === "string" && saved.trim() ? saved.trim() : void 0;
334
+ if (persisted) {
335
+ registry.keys.set(appId, persisted);
336
+ return adopted(persisted);
337
+ }
338
+ return Promise.resolve(storage.setItem(slot, minted)).then(
339
+ () => {
340
+ var _a;
341
+ return adopted((_a = registry.keys.get(appId)) != null ? _a : minted);
342
+ },
343
+ degraded
344
+ );
345
+ }).catch(degraded);
346
+ } catch {
347
+ run = Promise.resolve(degraded());
348
+ }
349
+ registry.pending.set(appId, run);
350
+ void run.then((outcome) => {
351
+ var _a;
352
+ if (outcome.durable) return;
353
+ (_a = registry.pending) == null ? void 0 : _a.delete(appId);
354
+ registry.hydrating.delete(appId);
355
+ });
356
+ return run;
357
+ };
358
+ var resolveAutoDeviceKey = (opts = {}) => {
359
+ var _a, _b;
360
+ const registry = autoDeviceKeyRegistry();
361
+ const appId = (_a = opts.appId) != null ? _a : "default";
362
+ let id = registry.keys.get(appId);
363
+ if (!id) {
364
+ id = mintDeviceId();
365
+ registry.keys.set(appId, id);
366
+ }
367
+ const storage = opts.storage;
368
+ if (storage && !registry.hydrating.has(appId)) {
369
+ registry.hydrating.add(appId);
370
+ void startHydration(registry, appId, storage, id);
371
+ }
372
+ return (_b = registry.keys.get(appId)) != null ? _b : id;
373
+ };
374
+ var resetAutoDeviceKeys = () => {
375
+ var _a;
376
+ const registry = autoDeviceKeyRegistry();
377
+ registry.keys.clear();
378
+ registry.hydrating.clear();
379
+ (_a = registry.pending) == null ? void 0 : _a.clear();
380
+ registry.ambient = void 0;
381
+ };
382
+
229
383
  // src/analytics/wireDoctor.ts
230
384
  var PROBE_STORAGE_KEY = "wireai:doctor:probe";
231
385
  var PROBE_QUESTION_KEY = "wire_doctor_probe";
@@ -356,6 +510,66 @@ var checkRoundTrip = async (target) => {
356
510
  `the server DISCARDED the probe event: written=${(_a = ack.written) != null ? _a : "unknown"}, skipped=${ack.skipped}` + (ack.reasons.length > 0 ? `, reasons: ${ack.reasons.join(", ")}.` : ". It gave no reason; check the server's ingest log for this request.")
357
511
  );
358
512
  };
513
+ var checkJoinKey = async (join, storage, storageOk) => {
514
+ var _a;
515
+ if (!join) {
516
+ return check(
517
+ "join_key",
518
+ false,
519
+ "NOT EVALUATED: pass `join: { appId, userContext, autoJoinKey }` \u2014 the same values you pass <WireOnboarding> \u2014 so the doctor can tell whether this integration's sessions will be joinable. It is reported as a failure rather than skipped because every other check can pass while the funnel reads a permanent zero."
520
+ );
521
+ }
522
+ if (isUsableIdentityValue((_a = join.userContext) == null ? void 0 : _a.device_key)) {
523
+ return check(
524
+ "join_key",
525
+ true,
526
+ "user_context.device_key is supplied by the host, so onboarding sessions join the rest of the funnel."
527
+ );
528
+ }
529
+ if (join.autoJoinKey === false) {
530
+ return check(
531
+ "join_key",
532
+ false,
533
+ "no user_context.device_key AND autoJoinKey is false, so these onboarding sessions are UNLINKED by choice: they join nothing the app reports later and the activated funnel reads zero. Supply user_context.device_key, or drop autoJoinKey:false to let the kit inject its own."
534
+ );
535
+ }
536
+ if (!storage) {
537
+ return check(
538
+ "join_key",
539
+ false,
540
+ "no user_context.device_key, and the kit cannot auto-inject one without storage: an unpersisted key differs every launch, which corrupts the server's session counting rather than just leaving it empty, so the kit declines it. Pass AsyncStorage (or an MMKV wrapper) to <WireOnboarding>."
541
+ );
542
+ }
543
+ if (!storageOk) {
544
+ return check(
545
+ "join_key",
546
+ false,
547
+ "no user_context.device_key, and the storage check above FAILED \u2014 the kit refuses a non-durable auto key, so it will inject nothing and these sessions will not join. Fix the storage adapter first."
548
+ );
549
+ }
550
+ let persisted = null;
551
+ try {
552
+ persisted = await storage.getItem(deviceIdStorageKey(join.appId));
553
+ } catch {
554
+ return check(
555
+ "join_key",
556
+ false,
557
+ "no user_context.device_key, and reading the persisted auto key threw, so it is unknown whether the kit can supply one. Treat this as the storage adapter being unreliable."
558
+ );
559
+ }
560
+ if (isUsableIdentityValue(persisted)) {
561
+ return check(
562
+ "join_key",
563
+ true,
564
+ "no host device_key, but a durable auto join key is already persisted for this appId, so sessions join."
565
+ );
566
+ }
567
+ return check(
568
+ "join_key",
569
+ true,
570
+ "no host device_key and none persisted yet (first run): the kit will mint and persist its own on first mount, and the storage check above proved the write/read round trip that injection is gated on."
571
+ );
572
+ };
359
573
  var wireDoctor = async (options) => {
360
574
  if (typeof __DEV__ === "undefined" || !__DEV__) {
361
575
  return {
@@ -371,14 +585,18 @@ var wireDoctor = async (options) => {
371
585
  }
372
586
  try {
373
587
  const target = options == null ? void 0 : options.target;
374
- const checks = [checkTarget(target)];
375
- if (checks[0].ok && target) {
588
+ const targetCheck = checkTarget(target);
589
+ const checks = [targetCheck];
590
+ const targetUsable = targetCheck.ok && !!target;
591
+ if (targetUsable && target) {
376
592
  checks.push(await checkReachability(target.serverUrl));
377
- checks.push(await checkStorage(options == null ? void 0 : options.storage));
593
+ }
594
+ const storageCheck = await checkStorage(options == null ? void 0 : options.storage);
595
+ checks.push(storageCheck);
596
+ if (targetUsable && target) {
378
597
  checks.push(await checkRoundTrip(target));
379
- } else {
380
- checks.push(await checkStorage(options == null ? void 0 : options.storage));
381
598
  }
599
+ checks.push(await checkJoinKey(options == null ? void 0 : options.join, options == null ? void 0 : options.storage, storageCheck.ok));
382
600
  return { ok: checks.every((c) => c.ok), checks };
383
601
  } catch {
384
602
  return {
@@ -1029,108 +1247,6 @@ var resolveUserContext = (ctx = {}, opts = {}) => {
1029
1247
  return result;
1030
1248
  };
1031
1249
 
1032
- // src/identity/identityRecord.ts
1033
- var IDENTITY_PROVENANCE_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:identityProvenance");
1034
- var provenanceGlobal = globalThis;
1035
- var provenanceRegistry = () => {
1036
- const existing = provenanceGlobal[IDENTITY_PROVENANCE_SLOT];
1037
- if (existing) return existing;
1038
- const created = { host: /* @__PURE__ */ new Map() };
1039
- provenanceGlobal[IDENTITY_PROVENANCE_SLOT] = created;
1040
- return created;
1041
- };
1042
- var provenanceKey = (space, scope) => `${space}:${scope != null ? scope : "default"}`;
1043
- var resolveIdentity = (input) => {
1044
- var _a;
1045
- if (typeof input.value !== "string") return void 0;
1046
- const value = input.value.trim();
1047
- if (!value) return void 0;
1048
- const durable = (_a = input.durable) != null ? _a : input.source === "host";
1049
- {
1050
- provenanceRegistry().host.set(provenanceKey(input.space, input.scope), value);
1051
- }
1052
- return { value, space: input.space, source: input.source, durable };
1053
- };
1054
-
1055
- // src/context/deviceId.ts
1056
- var AUTO_DEVICE_ID_PREFIX = "wdev_";
1057
- var deviceIdStorageKey = (appId) => `wireai:analytics:deviceKey:${appId != null ? appId : "default"}`;
1058
- var randomChunk = () => Math.floor(Math.random() * 4294967296).toString(36).padStart(6, "0");
1059
- var mintDeviceId = () => {
1060
- const time = Date.now().toString(36);
1061
- return `${AUTO_DEVICE_ID_PREFIX}${time}_${randomChunk()}${randomChunk()}`;
1062
- };
1063
- var AUTO_DEVICE_KEY_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:autoDeviceKeys");
1064
- var deviceKeyGlobal = globalThis;
1065
- var autoDeviceKeyRegistry = () => {
1066
- const existing = deviceKeyGlobal[AUTO_DEVICE_KEY_SLOT];
1067
- if (existing) return existing;
1068
- const created = { keys: /* @__PURE__ */ new Map(), hydrating: /* @__PURE__ */ new Set() };
1069
- deviceKeyGlobal[AUTO_DEVICE_KEY_SLOT] = created;
1070
- return created;
1071
- };
1072
- var startHydration = (registry, appId, storage, minted) => {
1073
- if (!registry.pending) registry.pending = /* @__PURE__ */ new Map();
1074
- const existing = registry.pending.get(appId);
1075
- if (existing) return existing;
1076
- const slot = deviceIdStorageKey(appId);
1077
- const degraded = () => {
1078
- var _a;
1079
- return { value: (_a = registry.keys.get(appId)) != null ? _a : minted, durable: false };
1080
- };
1081
- const adopted = (value) => ({ value, durable: true });
1082
- let run;
1083
- try {
1084
- run = Promise.resolve(storage.getItem(slot)).then((saved) => {
1085
- const persisted = typeof saved === "string" && saved.trim() ? saved.trim() : void 0;
1086
- if (persisted) {
1087
- registry.keys.set(appId, persisted);
1088
- return adopted(persisted);
1089
- }
1090
- return Promise.resolve(storage.setItem(slot, minted)).then(
1091
- () => {
1092
- var _a;
1093
- return adopted((_a = registry.keys.get(appId)) != null ? _a : minted);
1094
- },
1095
- degraded
1096
- );
1097
- }).catch(degraded);
1098
- } catch {
1099
- run = Promise.resolve(degraded());
1100
- }
1101
- registry.pending.set(appId, run);
1102
- void run.then((outcome) => {
1103
- var _a;
1104
- if (outcome.durable) return;
1105
- (_a = registry.pending) == null ? void 0 : _a.delete(appId);
1106
- registry.hydrating.delete(appId);
1107
- });
1108
- return run;
1109
- };
1110
- var resolveAutoDeviceKey = (opts = {}) => {
1111
- var _a, _b;
1112
- const registry = autoDeviceKeyRegistry();
1113
- const appId = (_a = opts.appId) != null ? _a : "default";
1114
- let id = registry.keys.get(appId);
1115
- if (!id) {
1116
- id = mintDeviceId();
1117
- registry.keys.set(appId, id);
1118
- }
1119
- const storage = opts.storage;
1120
- if (storage && !registry.hydrating.has(appId)) {
1121
- registry.hydrating.add(appId);
1122
- void startHydration(registry, appId, storage, id);
1123
- }
1124
- return (_b = registry.keys.get(appId)) != null ? _b : id;
1125
- };
1126
- var resetAutoDeviceKeys = () => {
1127
- var _a;
1128
- const registry = autoDeviceKeyRegistry();
1129
- registry.keys.clear();
1130
- registry.hydrating.clear();
1131
- (_a = registry.pending) == null ? void 0 : _a.clear();
1132
- };
1133
-
1134
1250
  // src/analytics/analyticsFacade.ts
1135
1251
  var createAnalytics = (config, options = {}) => {
1136
1252
  var _a, _b, _c;
@@ -1283,30 +1399,75 @@ var createAnalytics = (config, options = {}) => {
1283
1399
  dispose: queue.dispose
1284
1400
  };
1285
1401
  };
1402
+ var stableAnalyticsFacade = (live) => ({
1403
+ track: (event, props) => {
1404
+ var _a;
1405
+ return (_a = live()) == null ? void 0 : _a.track(event, props);
1406
+ },
1407
+ screen: (name, props) => {
1408
+ var _a;
1409
+ return (_a = live()) == null ? void 0 : _a.screen(name, props);
1410
+ },
1411
+ identify: (userId, traits) => {
1412
+ var _a;
1413
+ return (_a = live()) == null ? void 0 : _a.identify(userId, traits);
1414
+ },
1415
+ setUserContext: (partial) => {
1416
+ var _a;
1417
+ return (_a = live()) == null ? void 0 : _a.setUserContext(partial);
1418
+ },
1419
+ reset: () => {
1420
+ var _a;
1421
+ return (_a = live()) == null ? void 0 : _a.reset();
1422
+ },
1423
+ flush: () => {
1424
+ var _a;
1425
+ return (_a = live()) == null ? void 0 : _a.flush();
1426
+ },
1427
+ notifyOnline: () => {
1428
+ var _a;
1429
+ return (_a = live()) == null ? void 0 : _a.notifyOnline();
1430
+ },
1431
+ // After a real unmount there is no instance and nothing pending on it — 0 is the honest answer.
1432
+ size: () => {
1433
+ var _a, _b;
1434
+ return (_b = (_a = live()) == null ? void 0 : _a.size()) != null ? _b : 0;
1435
+ },
1436
+ dispose: () => {
1437
+ var _a;
1438
+ return (_a = live()) == null ? void 0 : _a.dispose();
1439
+ }
1440
+ });
1286
1441
  var useAnalytics = (config, options = {}) => {
1287
1442
  var _a, _b;
1288
1443
  const ref = useRef(void 0);
1289
1444
  const prevKeys = useRef("");
1445
+ const inputs = useRef({ config, options });
1446
+ inputs.current = { config, options };
1290
1447
  const currentKeys = `${config.serverUrl}|${config.apiKey}|${config.appId}`;
1291
1448
  if (!ref.current || prevKeys.current !== currentKeys) {
1292
1449
  prevKeys.current = currentKeys;
1293
1450
  (_a = ref.current) == null ? void 0 : _a.dispose();
1294
1451
  ref.current = createAnalytics(config, options);
1295
1452
  }
1453
+ const facade = useRef(void 0);
1454
+ if (!facade.current) facade.current = stableAnalyticsFacade(() => ref.current);
1455
+ useEffect(() => {
1456
+ if (!ref.current) {
1457
+ ref.current = createAnalytics(inputs.current.config, inputs.current.options);
1458
+ }
1459
+ return () => {
1460
+ var _a2;
1461
+ (_a2 = ref.current) == null ? void 0 : _a2.dispose();
1462
+ ref.current = void 0;
1463
+ };
1464
+ }, []);
1296
1465
  const hostDeviceKey = typeof ((_b = config.userContext) == null ? void 0 : _b.deviceKey) === "string" && config.userContext.deviceKey.trim() ? config.userContext.deviceKey.trim() : void 0;
1297
1466
  useEffect(() => {
1298
1467
  var _a2;
1299
1468
  if (hostDeviceKey) (_a2 = ref.current) == null ? void 0 : _a2.setUserContext({ deviceKey: hostDeviceKey });
1300
1469
  }, [hostDeviceKey]);
1301
- useEffect(
1302
- () => () => {
1303
- var _a2;
1304
- (_a2 = ref.current) == null ? void 0 : _a2.dispose();
1305
- ref.current = void 0;
1306
- },
1307
- []
1308
- );
1309
- return ref.current;
1470
+ return facade.current;
1310
1471
  };
1311
1472
 
1312
1473
  export { AUTO_DEVICE_ID_PREFIX, WIRE_ONBOARDING_EVENTS, analyticsUserIdStorageKey, buildContextEnvelope, clearPiiFromContext, clearUserContext, createAnalytics, createEventQueue, createScreenTracker, deviceIdStorageKey, ensureCurrentSessionId, getActiveRouteName, getCurrentSessionId, looksLikeEmail, makeSessionId, reportAppEvent, reportClientEvent, reportClientEventAwait, reportClientEvents, reportClientEventsAwait, resetAutoDeviceKeys, resetCurrentSessionId, resetEventQueueKeys, resolveAutoDeviceKey, screenTrackingHandler, setCurrentSessionId, toAnalyticsEvent, useAnalytics, useScreenTracking, wireDoctor };