@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.mjs CHANGED
@@ -1162,6 +1162,28 @@ var readProgress = (response) => {
1162
1162
  };
1163
1163
  };
1164
1164
 
1165
+ // src/utils/withDeadline.ts
1166
+ var DEADLINE_EXPIRED = /* @__PURE__ */ Symbol("wire-deadline-expired");
1167
+ var DEFAULT_DEADLINE_MS = 15e3;
1168
+ var withDeadline = async (exchange, timeoutMs = DEFAULT_DEADLINE_MS) => {
1169
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
1170
+ let timer;
1171
+ const expired = new Promise((resolve) => {
1172
+ timer = setTimeout(() => {
1173
+ controller == null ? void 0 : controller.abort();
1174
+ resolve(DEADLINE_EXPIRED);
1175
+ }, timeoutMs);
1176
+ });
1177
+ const running = exchange(controller == null ? void 0 : controller.signal);
1178
+ try {
1179
+ return await Promise.race([running, expired]);
1180
+ } finally {
1181
+ clearTimeout(timer);
1182
+ void running.catch(() => {
1183
+ });
1184
+ }
1185
+ };
1186
+
1165
1187
  // src/analytics/reportClientEvent.ts
1166
1188
  var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
1167
1189
  var toWireEvents = (events) => events.map((event) => {
@@ -1233,14 +1255,17 @@ var reportClientEventsOutcome = async (target, events) => {
1233
1255
  try {
1234
1256
  const req = buildEventsRequest(target, events);
1235
1257
  if (!req) return "refused";
1236
- const res = await fetch(req.url, req.init);
1237
- if (!res || !res.ok) return "unreachable";
1238
- const ack = await readEventsAck(res);
1239
- if (!ack || ack.skipped <= 0) return "delivered";
1240
- if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
1241
- console.warn(describeDiscarded(ack));
1242
- }
1243
- return "refused";
1258
+ const outcome = await withDeadline(async (signal) => {
1259
+ const res = await fetch(req.url, { ...req.init, signal });
1260
+ if (!res || !res.ok) return "unreachable";
1261
+ const ack = await readEventsAck(res);
1262
+ if (!ack || ack.skipped <= 0) return "delivered";
1263
+ if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
1264
+ console.warn(describeDiscarded(ack));
1265
+ }
1266
+ return "refused";
1267
+ });
1268
+ return outcome === DEADLINE_EXPIRED ? "unreachable" : outcome;
1244
1269
  } catch {
1245
1270
  return "unreachable";
1246
1271
  }
@@ -3472,11 +3497,11 @@ var provenanceRegistry = () => {
3472
3497
  return created;
3473
3498
  };
3474
3499
  var provenanceKey = (space, scope) => `${space}:${scope != null ? scope : "default"}`;
3500
+ var isUsableIdentityValue = (value) => typeof value === "string" && value.trim().length > 0;
3475
3501
  var resolveIdentity = (input) => {
3476
3502
  var _a;
3477
- if (typeof input.value !== "string") return void 0;
3503
+ if (!isUsableIdentityValue(input.value)) return void 0;
3478
3504
  const value = input.value.trim();
3479
- if (!value) return void 0;
3480
3505
  const durable = (_a = input.durable) != null ? _a : input.source === "host";
3481
3506
  if (input.source === "host") {
3482
3507
  provenanceRegistry().host.set(provenanceKey(input.space, input.scope), value);
@@ -3581,6 +3606,7 @@ var resetAutoDeviceKeys = () => {
3581
3606
  registry.keys.clear();
3582
3607
  registry.hydrating.clear();
3583
3608
  (_a = registry.pending) == null ? void 0 : _a.clear();
3609
+ registry.ambient = void 0;
3584
3610
  };
3585
3611
 
3586
3612
  // src/analytics/currentSession.ts
@@ -3588,25 +3614,51 @@ var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
3588
3614
  "@wireai/activation:currentSessionId"
3589
3615
  );
3590
3616
  var globalSlot = globalThis;
3617
+ var SESSION_ID_LISTENERS_SLOT = /* @__PURE__ */ Symbol.for(
3618
+ "@wireai/activation:currentSessionIdListeners"
3619
+ );
3620
+ var listenerSlot = globalThis;
3621
+ var listeners = () => {
3622
+ const existing = listenerSlot[SESSION_ID_LISTENERS_SLOT];
3623
+ if (existing) return existing;
3624
+ const created = /* @__PURE__ */ new Set();
3625
+ listenerSlot[SESSION_ID_LISTENERS_SLOT] = created;
3626
+ return created;
3627
+ };
3628
+ var writeCurrentSessionId = (id) => {
3629
+ if (globalSlot[CURRENT_SESSION_ID_SLOT] === id) return;
3630
+ globalSlot[CURRENT_SESSION_ID_SLOT] = id;
3631
+ for (const listener of Array.from(listeners())) {
3632
+ try {
3633
+ listener();
3634
+ } catch {
3635
+ }
3636
+ }
3637
+ };
3591
3638
  var setCurrentSessionId = (id) => {
3592
3639
  if (typeof id === "string" && id.length > 0) {
3593
- globalSlot[CURRENT_SESSION_ID_SLOT] = id;
3640
+ writeCurrentSessionId(id);
3594
3641
  }
3595
3642
  };
3596
3643
  var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
3597
3644
  var resetCurrentSessionId = () => {
3598
- globalSlot[CURRENT_SESSION_ID_SLOT] = void 0;
3645
+ writeCurrentSessionId(void 0);
3599
3646
  };
3600
3647
  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.";
3601
3648
  var MINT_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
3602
3649
  "@wireai/activation:currentSessionIdMintWarned"
3603
3650
  );
3604
3651
  var warnSlot = globalThis;
3652
+ var MINTED_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
3653
+ "@wireai/activation:mintedSessionId"
3654
+ );
3655
+ var mintedSlot = globalThis;
3605
3656
  var ensureCurrentSessionId = () => {
3606
3657
  const existing = globalSlot[CURRENT_SESSION_ID_SLOT];
3607
3658
  if (typeof existing === "string" && existing.length > 0) return existing;
3608
3659
  const minted = makeSessionId();
3609
- globalSlot[CURRENT_SESSION_ID_SLOT] = minted;
3660
+ writeCurrentSessionId(minted);
3661
+ mintedSlot[MINTED_SESSION_ID_SLOT] = minted;
3610
3662
  if (!warnSlot[MINT_WARNED_SLOT] && warnInDev(MINT_WARNING)) {
3611
3663
  warnSlot[MINT_WARNED_SLOT] = true;
3612
3664
  }
@@ -4446,17 +4498,31 @@ var failOpen = async (storage, key) => {
4446
4498
  const cached2 = await readCachedFeatures(storage, key);
4447
4499
  return (_a = cached2 == null ? void 0 : cached2.features) != null ? _a : defaultWireFeatures;
4448
4500
  };
4449
- var fetchWithTimeout = async (url, apiKey, timeoutMs) => {
4501
+ var NO_ANSWER = /* @__PURE__ */ Symbol("wire-features-no-answer");
4502
+ var fetchFeaturesJson = async (url, apiKey, timeoutMs) => {
4450
4503
  const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
4451
- const timer = setTimeout(() => controller == null ? void 0 : controller.abort(), timeoutMs);
4452
- try {
4453
- return await fetch(url, {
4504
+ let timer;
4505
+ const expired = new Promise((resolve) => {
4506
+ timer = setTimeout(() => {
4507
+ controller == null ? void 0 : controller.abort();
4508
+ resolve(NO_ANSWER);
4509
+ }, timeoutMs);
4510
+ });
4511
+ const exchange = (async () => {
4512
+ const res = await fetch(url, {
4454
4513
  method: "GET",
4455
4514
  headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
4456
4515
  signal: controller == null ? void 0 : controller.signal
4457
4516
  });
4517
+ if (!res || !res.ok) return NO_ANSWER;
4518
+ return await res.json();
4519
+ })();
4520
+ try {
4521
+ return await Promise.race([exchange, expired]);
4458
4522
  } finally {
4459
4523
  clearTimeout(timer);
4524
+ void exchange.catch(() => {
4525
+ });
4460
4526
  }
4461
4527
  };
4462
4528
  var fetchWireFeatures = async (config) => {
@@ -4466,9 +4532,8 @@ var fetchWireFeatures = async (config) => {
4466
4532
  const url = `${config.serverUrl.replace(/\/$/, "")}/v1/features`;
4467
4533
  const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
4468
4534
  try {
4469
- const res = await fetchWithTimeout(url, config.apiKey, timeoutMs);
4470
- if (!res || !res.ok) return failOpen(storage, key);
4471
- const json = await res.json();
4535
+ const json = await fetchFeaturesJson(url, config.apiKey, timeoutMs);
4536
+ if (json === NO_ANSWER) return failOpen(storage, key);
4472
4537
  const features = parseWireFeatures(json);
4473
4538
  if (storage) writeCachedFeatures(storage, key, features);
4474
4539
  return features;
@@ -4482,50 +4547,65 @@ var useStableValue = (value, isEqual) => {
4482
4547
  return ref.current;
4483
4548
  };
4484
4549
  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);
4485
- var useWireFeatures = (config) => {
4550
+ var useWireFeaturesState = (config) => {
4486
4551
  const stableConfig = useStableValue(config, sameFetchKey);
4487
4552
  const [flags, setFlags] = useState(defaultWireFeatures);
4488
4553
  const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
4554
+ const [settled, setSettled] = useState(!canFetch);
4489
4555
  useEffect(() => {
4490
4556
  if (!canFetch) {
4491
4557
  setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
4558
+ setSettled(true);
4492
4559
  return;
4493
4560
  }
4494
4561
  let alive = true;
4495
4562
  void fetchWireFeatures(stableConfig).then((next) => {
4496
4563
  if (!alive) return;
4497
4564
  setFlags((prev) => featuresEqual(prev, next) ? prev : next);
4565
+ setSettled(true);
4498
4566
  });
4499
4567
  return () => {
4500
4568
  alive = false;
4501
4569
  };
4502
4570
  }, [canFetch, stableConfig]);
4503
- return flags;
4571
+ return useMemo(() => ({ flags, settled }), [flags, settled]);
4504
4572
  };
4573
+ var useWireFeatures = (config) => useWireFeaturesState(config).flags;
4505
4574
  var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
4506
- var globalObj = globalThis;
4507
- if (!globalObj[CONTEXT_SYMBOL]) {
4508
- globalObj[CONTEXT_SYMBOL] = createContext(null);
4575
+ var featuresContextGlobal = globalThis;
4576
+ if (!featuresContextGlobal[CONTEXT_SYMBOL]) {
4577
+ featuresContextGlobal[CONTEXT_SYMBOL] = createContext(null);
4509
4578
  }
4510
- var WireFeaturesContext = globalObj[CONTEXT_SYMBOL];
4579
+ var WireFeaturesContext = featuresContextGlobal[CONTEXT_SYMBOL];
4580
+ var SETTLED_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.settled.context");
4581
+ var settledContextGlobal = globalThis;
4582
+ if (!settledContextGlobal[SETTLED_SYMBOL]) {
4583
+ settledContextGlobal[SETTLED_SYMBOL] = createContext(true);
4584
+ }
4585
+ var WireFeaturesSettledContext = settledContextGlobal[SETTLED_SYMBOL];
4511
4586
  var WireFeaturesProvider = ({
4512
4587
  config,
4513
4588
  flags,
4514
4589
  children
4515
4590
  }) => {
4516
- const fetched = useWireFeatures(flags ? void 0 : config);
4517
- const value = flags != null ? flags : fetched;
4591
+ const fetched = useWireFeaturesState(flags ? void 0 : config);
4592
+ const value = flags != null ? flags : fetched.flags;
4593
+ const settled = flags ? true : fetched.settled;
4518
4594
  const stable = useMemo(() => value, [value]);
4519
- return /* @__PURE__ */ jsx(WireFeaturesContext.Provider, { value: stable, children });
4595
+ return /* @__PURE__ */ jsx(WireFeaturesSettledContext.Provider, { value: settled, children: /* @__PURE__ */ jsx(WireFeaturesContext.Provider, { value: stable, children }) });
4520
4596
  };
4521
4597
  WireFeaturesProvider.displayName = "WireFeaturesProvider";
4522
4598
  var useWireFeaturesContext = () => useContext(WireFeaturesContext);
4523
- var useResolvedFeatures = (options) => {
4599
+ var useResolvedFeatures = (options) => useResolvedFeaturesState(options).flags;
4600
+ var useResolvedFeaturesState = (options) => {
4524
4601
  var _a, _b, _c;
4525
4602
  const ctx = useWireFeaturesContext();
4603
+ const ctxSettled = useContext(WireFeaturesSettledContext);
4526
4604
  const shouldFetch = !(options == null ? void 0 : options.flags) && ctx == null;
4527
- const fetched = useWireFeatures(shouldFetch ? options == null ? void 0 : options.config : void 0);
4528
- return (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched) != null ? _c : defaultWireFeatures;
4605
+ const fetched = useWireFeaturesState(shouldFetch ? options == null ? void 0 : options.config : void 0);
4606
+ const flags = (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched.flags) != null ? _c : defaultWireFeatures;
4607
+ const settled = (options == null ? void 0 : options.flags) ? true : ctx != null ? ctxSettled : fetched.settled;
4608
+ return useMemo(() => ({ flags, settled }), [flags, settled]);
4529
4609
  };
4530
4610
 
4531
4611
  // src/config/wireConfigFromEnv.ts
@@ -5269,8 +5349,12 @@ var useSessionStart = (config, options = {}) => {
5269
5349
  fireOpen(void 0);
5270
5350
  return;
5271
5351
  }
5272
- void hydrateDeviceIdentity({ appId: cfg.appId, storage: cfg.storage }).then((identity) => {
5352
+ void withTimeout(
5353
+ hydrateDeviceIdentity({ appId: cfg.appId, storage: cfg.storage }),
5354
+ READ_TIMEOUT_MS
5355
+ ).then((read) => {
5273
5356
  if (cancelled) return;
5357
+ const identity = read === READ_TIMED_OUT ? void 0 : read;
5274
5358
  fireOpen((identity == null ? void 0 : identity.durable) ? identity.value : void 0);
5275
5359
  });
5276
5360
  };
@@ -5370,8 +5454,9 @@ var emitFirstOpen = (opts) => {
5370
5454
  routeLifecycleEvent(buildLifecycleEvent(FIRST_OPEN_EVENT, opts), opts);
5371
5455
  };
5372
5456
  var reportFirstOpen = (opts) => {
5373
- var _a;
5374
- const appId = (_a = opts.appId) != null ? _a : "default";
5457
+ var _a, _b;
5458
+ if (!opts.sink && !((_a = opts.target) == null ? void 0 : _a.serverUrl)) return;
5459
+ const appId = (_b = opts.appId) != null ? _b : "default";
5375
5460
  if (_firstOpenLatched.has(appId)) return;
5376
5461
  _firstOpenLatched.add(appId);
5377
5462
  const storage = opts.storage;
@@ -5456,8 +5541,12 @@ var useLifecycleEvents = (config, options = {}) => {
5456
5541
  fire(void 0);
5457
5542
  return;
5458
5543
  }
5459
- void hydrateDeviceIdentity({ appId: cfg.appId, storage: cfg.storage }).then((identity) => {
5544
+ void withTimeout(
5545
+ hydrateDeviceIdentity({ appId: cfg.appId, storage: cfg.storage }),
5546
+ READ_TIMEOUT_MS
5547
+ ).then((read) => {
5460
5548
  if (cancelled) return;
5549
+ const identity = read === READ_TIMED_OUT ? void 0 : read;
5461
5550
  fire((identity == null ? void 0 : identity.durable) ? identity.value : void 0);
5462
5551
  });
5463
5552
  };