@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
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, createElement, createContext, useEffect, useMemo, useRef, useCallback, useState, useContext, useReducer } from 'react';
1
+ import React, { forwardRef, createElement, createContext, useEffect, useMemo, useState, useRef, useCallback, useContext, useReducer } from 'react';
2
2
  import { View, StyleSheet, AccessibilityInfo } from 'react-native';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
 
@@ -528,17 +528,31 @@ var failOpen = async (storage, key) => {
528
528
  const cached3 = await readCachedFeatures(storage, key);
529
529
  return (_a = cached3 == null ? void 0 : cached3.features) != null ? _a : defaultWireFeatures;
530
530
  };
531
- var fetchWithTimeout = async (url, apiKey, timeoutMs) => {
531
+ var NO_ANSWER = /* @__PURE__ */ Symbol("wire-features-no-answer");
532
+ var fetchFeaturesJson = async (url, apiKey, timeoutMs) => {
532
533
  const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
533
- const timer = setTimeout(() => controller == null ? void 0 : controller.abort(), timeoutMs);
534
- try {
535
- return await fetch(url, {
534
+ let timer;
535
+ const expired = new Promise((resolve) => {
536
+ timer = setTimeout(() => {
537
+ controller == null ? void 0 : controller.abort();
538
+ resolve(NO_ANSWER);
539
+ }, timeoutMs);
540
+ });
541
+ const exchange = (async () => {
542
+ const res = await fetch(url, {
536
543
  method: "GET",
537
544
  headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
538
545
  signal: controller == null ? void 0 : controller.signal
539
546
  });
547
+ if (!res || !res.ok) return NO_ANSWER;
548
+ return await res.json();
549
+ })();
550
+ try {
551
+ return await Promise.race([exchange, expired]);
540
552
  } finally {
541
553
  clearTimeout(timer);
554
+ void exchange.catch(() => {
555
+ });
542
556
  }
543
557
  };
544
558
  var fetchWireFeatures = async (config) => {
@@ -548,9 +562,8 @@ var fetchWireFeatures = async (config) => {
548
562
  const url = `${config.serverUrl.replace(/\/$/, "")}/v1/features`;
549
563
  const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
550
564
  try {
551
- const res = await fetchWithTimeout(url, config.apiKey, timeoutMs);
552
- if (!res || !res.ok) return failOpen(storage, key);
553
- const json = await res.json();
565
+ const json = await fetchFeaturesJson(url, config.apiKey, timeoutMs);
566
+ if (json === NO_ANSWER) return failOpen(storage, key);
554
567
  const features = parseWireFeatures(json);
555
568
  if (storage) writeCachedFeatures(storage, key, features);
556
569
  return features;
@@ -566,41 +579,56 @@ var useStableValue = (value, isEqual) => {
566
579
  return ref.current;
567
580
  };
568
581
  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);
569
- var useWireFeatures = (config) => {
582
+ var useWireFeaturesState = (config) => {
570
583
  const stableConfig = useStableValue(config, sameFetchKey);
571
584
  const [flags, setFlags] = useState(defaultWireFeatures);
572
585
  const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
586
+ const [settled, setSettled] = useState(!canFetch);
573
587
  useEffect(() => {
574
588
  if (!canFetch) {
575
589
  setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
590
+ setSettled(true);
576
591
  return;
577
592
  }
578
593
  let alive = true;
579
594
  void fetchWireFeatures(stableConfig).then((next) => {
580
595
  if (!alive) return;
581
596
  setFlags((prev) => featuresEqual(prev, next) ? prev : next);
597
+ setSettled(true);
582
598
  });
583
599
  return () => {
584
600
  alive = false;
585
601
  };
586
602
  }, [canFetch, stableConfig]);
587
- return flags;
603
+ return useMemo(() => ({ flags, settled }), [flags, settled]);
588
604
  };
589
605
  var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
590
- var globalObj = globalThis;
591
- if (!globalObj[CONTEXT_SYMBOL]) {
592
- globalObj[CONTEXT_SYMBOL] = createContext(null);
606
+ var featuresContextGlobal = globalThis;
607
+ if (!featuresContextGlobal[CONTEXT_SYMBOL]) {
608
+ featuresContextGlobal[CONTEXT_SYMBOL] = createContext(null);
593
609
  }
594
- var WireFeaturesContext = globalObj[CONTEXT_SYMBOL];
610
+ var WireFeaturesContext = featuresContextGlobal[CONTEXT_SYMBOL];
611
+ var SETTLED_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.settled.context");
612
+ var settledContextGlobal = globalThis;
613
+ if (!settledContextGlobal[SETTLED_SYMBOL]) {
614
+ settledContextGlobal[SETTLED_SYMBOL] = createContext(true);
615
+ }
616
+ var WireFeaturesSettledContext = settledContextGlobal[SETTLED_SYMBOL];
595
617
  var useWireFeaturesContext = () => useContext(WireFeaturesContext);
596
- var useResolvedFeatures = (options) => {
618
+ var useResolvedFeaturesState = (options) => {
597
619
  var _a, _b, _c;
598
620
  const ctx = useWireFeaturesContext();
621
+ const ctxSettled = useContext(WireFeaturesSettledContext);
599
622
  const shouldFetch = !(options == null ? void 0 : options.flags) && ctx == null;
600
- const fetched = useWireFeatures(shouldFetch ? options == null ? void 0 : options.config : void 0);
601
- return (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched) != null ? _c : defaultWireFeatures;
623
+ const fetched = useWireFeaturesState(shouldFetch ? options == null ? void 0 : options.config : void 0);
624
+ const flags = (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched.flags) != null ? _c : defaultWireFeatures;
625
+ const settled = (options == null ? void 0 : options.flags) ? true : ctx != null ? ctxSettled : fetched.settled;
626
+ return useMemo(() => ({ flags, settled }), [flags, settled]);
602
627
  };
603
628
 
629
+ // src/session/persistedSession.ts
630
+ var READ_TIMEOUT_MS2 = 1500;
631
+
604
632
  // src/theme/defaultTheme.ts
605
633
  var defaultOnboardingTheme = {
606
634
  colors: {
@@ -760,7 +788,10 @@ var _FeatureShowcase = ({
760
788
  [active, themeOverride]
761
789
  );
762
790
  const accent = accentColor != null ? accentColor : t.colors.primary;
763
- const flags = useResolvedFeatures({ flags: features, config: featuresConfig });
791
+ const { flags, settled } = useResolvedFeaturesState({
792
+ flags: features,
793
+ config: featuresConfig
794
+ });
764
795
  const disabled = !flags.showcase.enabled;
765
796
  const Onboarding = useMemo(() => resolveShowcaseOnboarding(), []);
766
797
  const gateKey = showcaseGateKey(config.id);
@@ -769,6 +800,14 @@ var _FeatureShowcase = ({
769
800
  [gateKey, storage, isTesting]
770
801
  );
771
802
  const skip = seen || disabled || !Onboarding;
803
+ const flagsCouldMatter = !seen && !!Onboarding;
804
+ const [holdExpired, setHoldExpired] = useState(false);
805
+ useEffect(() => {
806
+ if (settled || holdExpired || !flagsCouldMatter) return void 0;
807
+ const timer = setTimeout(() => setHoldExpired(true), READ_TIMEOUT_MS2);
808
+ return () => clearTimeout(timer);
809
+ }, [settled, holdExpired, flagsCouldMatter]);
810
+ const holding = flagsCouldMatter && !settled && !holdExpired;
772
811
  const doneRef = useRef(false);
773
812
  const finish = useCallback(() => {
774
813
  if (doneRef.current) return;
@@ -777,11 +816,12 @@ var _FeatureShowcase = ({
777
816
  onDone();
778
817
  }, [gateKey, storage, isTesting, onDone]);
779
818
  useEffect(() => {
819
+ if (holding) return;
780
820
  if (skip && !doneRef.current) {
781
821
  doneRef.current = true;
782
822
  onDone();
783
823
  }
784
- }, [skip, onDone]);
824
+ }, [holding, skip, onDone]);
785
825
  const [activeIndex, setActiveIndex] = useState(0);
786
826
  const steps = useMemo(
787
827
  () => config.slides.map((slide) => {
@@ -848,6 +888,7 @@ var _FeatureShowcase = ({
848
888
  }),
849
889
  [t]
850
890
  );
891
+ if (holding) return null;
851
892
  if (skip || !Onboarding) return null;
852
893
  const activeGesture = (_a = config.slides[activeIndex]) == null ? void 0 : _a.gesture;
853
894
  const showGesture = !!activeGesture && activeGesture !== "none" && activeGesture !== "tap" && activeGesture !== "double_tap";