@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
@@ -534,17 +534,31 @@ var failOpen = async (storage, key) => {
534
534
  const cached3 = await readCachedFeatures(storage, key);
535
535
  return (_a = cached3 == null ? void 0 : cached3.features) != null ? _a : defaultWireFeatures;
536
536
  };
537
- var fetchWithTimeout = async (url, apiKey, timeoutMs) => {
537
+ var NO_ANSWER = /* @__PURE__ */ Symbol("wire-features-no-answer");
538
+ var fetchFeaturesJson = async (url, apiKey, timeoutMs) => {
538
539
  const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
539
- const timer = setTimeout(() => controller == null ? void 0 : controller.abort(), timeoutMs);
540
- try {
541
- return await fetch(url, {
540
+ let timer;
541
+ const expired = new Promise((resolve) => {
542
+ timer = setTimeout(() => {
543
+ controller == null ? void 0 : controller.abort();
544
+ resolve(NO_ANSWER);
545
+ }, timeoutMs);
546
+ });
547
+ const exchange = (async () => {
548
+ const res = await fetch(url, {
542
549
  method: "GET",
543
550
  headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
544
551
  signal: controller == null ? void 0 : controller.signal
545
552
  });
553
+ if (!res || !res.ok) return NO_ANSWER;
554
+ return await res.json();
555
+ })();
556
+ try {
557
+ return await Promise.race([exchange, expired]);
546
558
  } finally {
547
559
  clearTimeout(timer);
560
+ void exchange.catch(() => {
561
+ });
548
562
  }
549
563
  };
550
564
  var fetchWireFeatures = async (config) => {
@@ -554,9 +568,8 @@ var fetchWireFeatures = async (config) => {
554
568
  const url = `${config.serverUrl.replace(/\/$/, "")}/v1/features`;
555
569
  const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
556
570
  try {
557
- const res = await fetchWithTimeout(url, config.apiKey, timeoutMs);
558
- if (!res || !res.ok) return failOpen(storage, key);
559
- const json = await res.json();
571
+ const json = await fetchFeaturesJson(url, config.apiKey, timeoutMs);
572
+ if (json === NO_ANSWER) return failOpen(storage, key);
560
573
  const features = parseWireFeatures(json);
561
574
  if (storage) writeCachedFeatures(storage, key, features);
562
575
  return features;
@@ -572,41 +585,56 @@ var useStableValue = (value, isEqual) => {
572
585
  return ref.current;
573
586
  };
574
587
  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);
575
- var useWireFeatures = (config) => {
588
+ var useWireFeaturesState = (config) => {
576
589
  const stableConfig = useStableValue(config, sameFetchKey);
577
590
  const [flags, setFlags] = React.useState(defaultWireFeatures);
578
591
  const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
592
+ const [settled, setSettled] = React.useState(!canFetch);
579
593
  React.useEffect(() => {
580
594
  if (!canFetch) {
581
595
  setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
596
+ setSettled(true);
582
597
  return;
583
598
  }
584
599
  let alive = true;
585
600
  void fetchWireFeatures(stableConfig).then((next) => {
586
601
  if (!alive) return;
587
602
  setFlags((prev) => featuresEqual(prev, next) ? prev : next);
603
+ setSettled(true);
588
604
  });
589
605
  return () => {
590
606
  alive = false;
591
607
  };
592
608
  }, [canFetch, stableConfig]);
593
- return flags;
609
+ return React.useMemo(() => ({ flags, settled }), [flags, settled]);
594
610
  };
595
611
  var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
596
- var globalObj = globalThis;
597
- if (!globalObj[CONTEXT_SYMBOL]) {
598
- globalObj[CONTEXT_SYMBOL] = React.createContext(null);
612
+ var featuresContextGlobal = globalThis;
613
+ if (!featuresContextGlobal[CONTEXT_SYMBOL]) {
614
+ featuresContextGlobal[CONTEXT_SYMBOL] = React.createContext(null);
599
615
  }
600
- var WireFeaturesContext = globalObj[CONTEXT_SYMBOL];
616
+ var WireFeaturesContext = featuresContextGlobal[CONTEXT_SYMBOL];
617
+ var SETTLED_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.settled.context");
618
+ var settledContextGlobal = globalThis;
619
+ if (!settledContextGlobal[SETTLED_SYMBOL]) {
620
+ settledContextGlobal[SETTLED_SYMBOL] = React.createContext(true);
621
+ }
622
+ var WireFeaturesSettledContext = settledContextGlobal[SETTLED_SYMBOL];
601
623
  var useWireFeaturesContext = () => React.useContext(WireFeaturesContext);
602
- var useResolvedFeatures = (options) => {
624
+ var useResolvedFeaturesState = (options) => {
603
625
  var _a, _b, _c;
604
626
  const ctx = useWireFeaturesContext();
627
+ const ctxSettled = React.useContext(WireFeaturesSettledContext);
605
628
  const shouldFetch = !(options == null ? void 0 : options.flags) && ctx == null;
606
- const fetched = useWireFeatures(shouldFetch ? options == null ? void 0 : options.config : void 0);
607
- return (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched) != null ? _c : defaultWireFeatures;
629
+ const fetched = useWireFeaturesState(shouldFetch ? options == null ? void 0 : options.config : void 0);
630
+ const flags = (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched.flags) != null ? _c : defaultWireFeatures;
631
+ const settled = (options == null ? void 0 : options.flags) ? true : ctx != null ? ctxSettled : fetched.settled;
632
+ return React.useMemo(() => ({ flags, settled }), [flags, settled]);
608
633
  };
609
634
 
635
+ // src/session/persistedSession.ts
636
+ var READ_TIMEOUT_MS2 = 1500;
637
+
610
638
  // src/theme/defaultTheme.ts
611
639
  var defaultOnboardingTheme = {
612
640
  colors: {
@@ -766,7 +794,10 @@ var _FeatureShowcase = ({
766
794
  [active, themeOverride]
767
795
  );
768
796
  const accent = accentColor != null ? accentColor : t.colors.primary;
769
- const flags = useResolvedFeatures({ flags: features, config: featuresConfig });
797
+ const { flags, settled } = useResolvedFeaturesState({
798
+ flags: features,
799
+ config: featuresConfig
800
+ });
770
801
  const disabled = !flags.showcase.enabled;
771
802
  const Onboarding = React.useMemo(() => resolveShowcaseOnboarding(), []);
772
803
  const gateKey = showcaseGateKey(config.id);
@@ -775,6 +806,14 @@ var _FeatureShowcase = ({
775
806
  [gateKey, storage, isTesting]
776
807
  );
777
808
  const skip = seen || disabled || !Onboarding;
809
+ const flagsCouldMatter = !seen && !!Onboarding;
810
+ const [holdExpired, setHoldExpired] = React.useState(false);
811
+ React.useEffect(() => {
812
+ if (settled || holdExpired || !flagsCouldMatter) return void 0;
813
+ const timer = setTimeout(() => setHoldExpired(true), READ_TIMEOUT_MS2);
814
+ return () => clearTimeout(timer);
815
+ }, [settled, holdExpired, flagsCouldMatter]);
816
+ const holding = flagsCouldMatter && !settled && !holdExpired;
778
817
  const doneRef = React.useRef(false);
779
818
  const finish = React.useCallback(() => {
780
819
  if (doneRef.current) return;
@@ -783,11 +822,12 @@ var _FeatureShowcase = ({
783
822
  onDone();
784
823
  }, [gateKey, storage, isTesting, onDone]);
785
824
  React.useEffect(() => {
825
+ if (holding) return;
786
826
  if (skip && !doneRef.current) {
787
827
  doneRef.current = true;
788
828
  onDone();
789
829
  }
790
- }, [skip, onDone]);
830
+ }, [holding, skip, onDone]);
791
831
  const [activeIndex, setActiveIndex] = React.useState(0);
792
832
  const steps = React.useMemo(
793
833
  () => config.slides.map((slide) => {
@@ -854,6 +894,7 @@ var _FeatureShowcase = ({
854
894
  }),
855
895
  [t]
856
896
  );
897
+ if (holding) return null;
857
898
  if (skip || !Onboarding) return null;
858
899
  const activeGesture = (_a = config.slides[activeIndex]) == null ? void 0 : _a.gesture;
859
900
  const showGesture = !!activeGesture && activeGesture !== "none" && activeGesture !== "tap" && activeGesture !== "double_tap";