@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
@@ -89,17 +89,31 @@ var failOpen = async (storage, key) => {
89
89
  const cached3 = await readCachedFeatures(storage, key);
90
90
  return (_a = cached3 == null ? void 0 : cached3.features) != null ? _a : defaultWireFeatures;
91
91
  };
92
- var fetchWithTimeout = async (url, apiKey, timeoutMs) => {
92
+ var NO_ANSWER = /* @__PURE__ */ Symbol("wire-features-no-answer");
93
+ var fetchFeaturesJson = async (url, apiKey, timeoutMs) => {
93
94
  const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
94
- const timer = setTimeout(() => controller == null ? void 0 : controller.abort(), timeoutMs);
95
- try {
96
- return await fetch(url, {
95
+ let timer;
96
+ const expired = new Promise((resolve) => {
97
+ timer = setTimeout(() => {
98
+ controller == null ? void 0 : controller.abort();
99
+ resolve(NO_ANSWER);
100
+ }, timeoutMs);
101
+ });
102
+ const exchange = (async () => {
103
+ const res = await fetch(url, {
97
104
  method: "GET",
98
105
  headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
99
106
  signal: controller == null ? void 0 : controller.signal
100
107
  });
108
+ if (!res || !res.ok) return NO_ANSWER;
109
+ return await res.json();
110
+ })();
111
+ try {
112
+ return await Promise.race([exchange, expired]);
101
113
  } finally {
102
114
  clearTimeout(timer);
115
+ void exchange.catch(() => {
116
+ });
103
117
  }
104
118
  };
105
119
  var fetchWireFeatures = async (config) => {
@@ -109,9 +123,8 @@ var fetchWireFeatures = async (config) => {
109
123
  const url = `${config.serverUrl.replace(/\/$/, "")}/v1/features`;
110
124
  const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
111
125
  try {
112
- const res = await fetchWithTimeout(url, config.apiKey, timeoutMs);
113
- if (!res || !res.ok) return failOpen(storage, key);
114
- const json = await res.json();
126
+ const json = await fetchFeaturesJson(url, config.apiKey, timeoutMs);
127
+ if (json === NO_ANSWER) return failOpen(storage, key);
115
128
  const features = parseWireFeatures(json);
116
129
  if (storage) writeCachedFeatures(storage, key, features);
117
130
  return features;
@@ -127,39 +140,51 @@ var useStableValue = (value, isEqual) => {
127
140
  return ref.current;
128
141
  };
129
142
  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);
130
- var useWireFeatures = (config) => {
143
+ var useWireFeaturesState = (config) => {
131
144
  const stableConfig = useStableValue(config, sameFetchKey);
132
145
  const [flags, setFlags] = useState(defaultWireFeatures);
133
146
  const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
147
+ const [settled, setSettled] = useState(!canFetch);
134
148
  useEffect(() => {
135
149
  if (!canFetch) {
136
150
  setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
151
+ setSettled(true);
137
152
  return;
138
153
  }
139
154
  let alive = true;
140
155
  void fetchWireFeatures(stableConfig).then((next) => {
141
156
  if (!alive) return;
142
157
  setFlags((prev) => featuresEqual(prev, next) ? prev : next);
158
+ setSettled(true);
143
159
  });
144
160
  return () => {
145
161
  alive = false;
146
162
  };
147
163
  }, [canFetch, stableConfig]);
148
- return flags;
164
+ return useMemo(() => ({ flags, settled }), [flags, settled]);
149
165
  };
150
166
  var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
151
- var globalObj = globalThis;
152
- if (!globalObj[CONTEXT_SYMBOL]) {
153
- globalObj[CONTEXT_SYMBOL] = createContext(null);
167
+ var featuresContextGlobal = globalThis;
168
+ if (!featuresContextGlobal[CONTEXT_SYMBOL]) {
169
+ featuresContextGlobal[CONTEXT_SYMBOL] = createContext(null);
154
170
  }
155
- var WireFeaturesContext = globalObj[CONTEXT_SYMBOL];
171
+ var WireFeaturesContext = featuresContextGlobal[CONTEXT_SYMBOL];
172
+ var SETTLED_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.settled.context");
173
+ var settledContextGlobal = globalThis;
174
+ if (!settledContextGlobal[SETTLED_SYMBOL]) {
175
+ settledContextGlobal[SETTLED_SYMBOL] = createContext(true);
176
+ }
177
+ var WireFeaturesSettledContext = settledContextGlobal[SETTLED_SYMBOL];
156
178
  var useWireFeaturesContext = () => useContext(WireFeaturesContext);
157
- var useResolvedFeatures = (options) => {
179
+ var useResolvedFeaturesState = (options) => {
158
180
  var _a, _b, _c;
159
181
  const ctx = useWireFeaturesContext();
182
+ const ctxSettled = useContext(WireFeaturesSettledContext);
160
183
  const shouldFetch = !(options == null ? void 0 : options.flags) && ctx == null;
161
- const fetched = useWireFeatures(shouldFetch ? options == null ? void 0 : options.config : void 0);
162
- return (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched) != null ? _c : defaultWireFeatures;
184
+ const fetched = useWireFeaturesState(shouldFetch ? options == null ? void 0 : options.config : void 0);
185
+ const flags = (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched.flags) != null ? _c : defaultWireFeatures;
186
+ const settled = (options == null ? void 0 : options.flags) ? true : ctx != null ? ctxSettled : fetched.settled;
187
+ return useMemo(() => ({ flags, settled }), [flags, settled]);
163
188
  };
164
189
 
165
190
  // src/coachmarks/runtime.ts
@@ -195,6 +220,17 @@ var setCoachmarksEnabled = (value) => {
195
220
  notifyCoachmarksEnabled();
196
221
  };
197
222
  var areCoachmarksEnabled = () => coachmarkRuntime().coachmarksEnabled;
223
+ var setCoachmarksResolved = (value) => {
224
+ var _a;
225
+ const runtime = coachmarkRuntime();
226
+ if (((_a = runtime.coachmarksResolved) != null ? _a : true) === value) return;
227
+ runtime.coachmarksResolved = value;
228
+ notifyCoachmarksEnabled();
229
+ };
230
+ var areCoachmarksResolved = () => {
231
+ var _a;
232
+ return (_a = coachmarkRuntime().coachmarksResolved) != null ? _a : true;
233
+ };
198
234
  var subscribeCoachmarksEnabled = (listener) => {
199
235
  const listeners2 = enabledListeners();
200
236
  listeners2.add(listener);
@@ -1007,22 +1043,28 @@ var CoachmarkProvider = ({
1007
1043
  featuresConfig,
1008
1044
  children
1009
1045
  }) => {
1010
- const flags = useResolvedFeatures({ flags: features, config: featuresConfig });
1011
- const coachmarksOn = flags.coachmarks.enabled;
1046
+ const { flags, settled } = useResolvedFeaturesState({
1047
+ flags: features,
1048
+ config: featuresConfig
1049
+ });
1050
+ const coachmarksOn = settled ? flags.coachmarks.enabled : true;
1012
1051
  setCoachmarkStorage(storage);
1013
1052
  setCoachmarkTesting(isTestingCoachmark);
1053
+ setCoachmarksResolved(settled);
1014
1054
  setCoachmarksEnabled(coachmarksOn);
1015
1055
  useEffect(() => {
1016
1056
  setCoachmarkStorage(storage);
1017
1057
  setCoachmarkTesting(isTestingCoachmark);
1058
+ setCoachmarksResolved(settled);
1018
1059
  setCoachmarksEnabled(coachmarksOn);
1019
1060
  return () => {
1020
1061
  if (getCoachmarkStorage() === storage) {
1021
1062
  setCoachmarkStorage(null);
1022
1063
  }
1023
1064
  setCoachmarksEnabled(true);
1065
+ setCoachmarksResolved(true);
1024
1066
  };
1025
- }, [storage, isTestingCoachmark, coachmarksOn]);
1067
+ }, [storage, isTestingCoachmark, coachmarksOn, settled]);
1026
1068
  return /* @__PURE__ */ jsxs(Fragment, { children: [
1027
1069
  children,
1028
1070
  /* @__PURE__ */ jsx(
@@ -1125,6 +1167,11 @@ var useCoachmarkTour = (steps, {
1125
1167
  areCoachmarksEnabled,
1126
1168
  areCoachmarksEnabled
1127
1169
  );
1170
+ const coachmarksResolved = useSyncExternalStore(
1171
+ subscribeCoachmarksEnabled,
1172
+ areCoachmarksResolved,
1173
+ areCoachmarksResolved
1174
+ );
1128
1175
  const exit = useCallback(
1129
1176
  (writeGate) => {
1130
1177
  if (finishedRef.current) return;
@@ -1139,14 +1186,16 @@ var useCoachmarkTour = (steps, {
1139
1186
  const finish = useCallback(() => exit(true), [exit]);
1140
1187
  useEffect(() => {
1141
1188
  if (!enabled || startedRef.current) return void 0;
1189
+ if (steps.length === 0) return void 0;
1142
1190
  if (!coachmarksOn) return void 0;
1191
+ if (!coachmarksResolved) return void 0;
1143
1192
  if (showOnce && hasSeenGate(coachmarkGateKey(tourId))) return void 0;
1144
1193
  const timer = setTimeout(() => {
1145
1194
  startedRef.current = true;
1146
1195
  setActiveIndex(0);
1147
1196
  }, startDelayMs);
1148
1197
  return () => clearTimeout(timer);
1149
- }, [enabled, coachmarksOn, startDelayMs, showOnce, tourId]);
1198
+ }, [enabled, coachmarksOn, coachmarksResolved, startDelayMs, showOnce, tourId, steps.length]);
1150
1199
  useEffect(() => {
1151
1200
  if (activeIndex === null) return void 0;
1152
1201
  if (!coachmarksOn) {
@@ -1157,6 +1206,10 @@ var useCoachmarkTour = (steps, {
1157
1206
  if (startedRef.current && !finishedRef.current) coachmarkOverlay.hide();
1158
1207
  return void 0;
1159
1208
  }
1209
+ if (steps.length === 0) {
1210
+ exit(false);
1211
+ return void 0;
1212
+ }
1160
1213
  if (activeIndex >= steps.length) {
1161
1214
  finish();
1162
1215
  return void 0;