@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
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
  };