@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.
- package/AGENTS.md +96 -21
- package/CHANGELOG.md +780 -0
- package/INTEGRATION_PROMPT.md +61 -23
- package/README.md +110 -31
- package/dist/analytics/index.d.mts +35 -13
- package/dist/analytics/index.d.ts +35 -13
- package/dist/analytics/index.js +288 -127
- package/dist/analytics/index.js.map +1 -1
- package/dist/analytics/index.mjs +288 -127
- package/dist/analytics/index.mjs.map +1 -1
- package/dist/coachmarks/index.d.mts +15 -1
- package/dist/coachmarks/index.d.ts +15 -1
- package/dist/coachmarks/index.js +120 -22
- package/dist/coachmarks/index.js.map +1 -1
- package/dist/coachmarks/index.mjs +120 -22
- package/dist/coachmarks/index.mjs.map +1 -1
- package/dist/{currentSession-CUvTOchb.d.mts → currentSession-Bz7G6lno.d.mts} +35 -39
- package/dist/{currentSession-CW_5Mq4O.d.ts → currentSession-z-CZ55ad.d.ts} +35 -39
- package/dist/{decision-Bgo17oH7.d.mts → decision-3vWLuBlO.d.ts} +11 -2
- package/dist/{decision-Bkh_LigV.d.ts → decision-yBj2AyPW.d.mts} +11 -2
- package/dist/index.d.mts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +125 -36
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +125 -36
- package/dist/index.mjs.map +1 -1
- package/dist/questionnaire/index.d.mts +3 -16
- package/dist/questionnaire/index.d.ts +3 -16
- package/dist/questionnaire/index.js +202 -46
- package/dist/questionnaire/index.js.map +1 -1
- package/dist/questionnaire/index.mjs +203 -47
- package/dist/questionnaire/index.mjs.map +1 -1
- package/dist/reviews/index.d.mts +10 -6
- package/dist/reviews/index.d.ts +10 -6
- package/dist/reviews/index.js +269 -52
- package/dist/reviews/index.js.map +1 -1
- package/dist/reviews/index.mjs +270 -53
- package/dist/reviews/index.mjs.map +1 -1
- package/dist/showcase/index.d.mts +1 -1
- package/dist/showcase/index.d.ts +1 -1
- package/dist/showcase/index.js +106 -20
- package/dist/showcase/index.js.map +1 -1
- package/dist/showcase/index.mjs +107 -21
- package/dist/showcase/index.mjs.map +1 -1
- package/dist/{transport-j5gFfJhK.d.mts → transport-s5QxA-ci.d.mts} +18 -13
- package/dist/{transport-B_0SgCBe.d.ts → transport-xqqSFqxs.d.ts} +18 -13
- package/dist/{types-Cju-1_jT.d.mts → types-Byx306Kv.d.mts} +25 -10
- package/dist/{types-BcmagF6K.d.mts → types-D_0B0yay.d.mts} +7 -2
- package/dist/{types-BcmagF6K.d.ts → types-D_0B0yay.d.ts} +7 -2
- package/dist/{types-h2BZvl1t.d.ts → types-tdATL5z0.d.ts} +25 -10
- package/llms.txt +9 -9
- package/package.json +6 -9
- package/src/WireOnboarding.tsx +4 -1
- package/src/analytics/currentSession.ts +141 -4
- package/src/analytics/index.ts +6 -1
- package/src/analytics/reportClientEvent.ts +19 -10
- package/src/analytics/useAnalytics.ts +74 -15
- package/src/analytics/wireDoctor.ts +152 -7
- package/src/coachmarks/CoachmarkProvider.tsx +26 -5
- package/src/coachmarks/runtime.ts +156 -2
- package/src/coachmarks/types.ts +7 -2
- package/src/coachmarks/useCoachmarkTour.ts +51 -1
- package/src/context/deviceId.ts +72 -6
- package/src/features/WireFeaturesProvider.tsx +72 -12
- package/src/features/fetchWireFeatures.ts +49 -11
- package/src/features/useWireFeatures.ts +39 -3
- package/src/identity/identityRecord.ts +15 -2
- package/src/questionnaire/QuestionnaireGate.tsx +40 -1
- package/src/questionnaire/transport.ts +22 -8
- package/src/questionnaire/useQuestionnaireGate.ts +58 -7
- package/src/reviews/ReviewGate.tsx +125 -37
- package/src/reviews/decision.ts +11 -1
- package/src/reviews/idempotency.ts +109 -0
- package/src/reviews/index.ts +4 -2
- package/src/reviews/runtime.ts +44 -13
- package/src/reviews/transport.ts +39 -20
- package/src/reviews/types.ts +7 -0
- package/src/reviews/useReviewGate.ts +57 -7
- package/src/session-analytics/lifecycle.ts +16 -0
- package/src/session-analytics/useLifecycleEvents.ts +30 -2
- package/src/session-analytics/useSessionStart.ts +22 -2
- package/src/showcase/FeatureShowcase.tsx +50 -3
- package/src/types.ts +15 -8
- package/src/utils/submitResult.ts +17 -9
- 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
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
113
|
-
if (
|
|
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
|
|
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
|
|
152
|
-
if (!
|
|
153
|
-
|
|
167
|
+
var featuresContextGlobal = globalThis;
|
|
168
|
+
if (!featuresContextGlobal[CONTEXT_SYMBOL]) {
|
|
169
|
+
featuresContextGlobal[CONTEXT_SYMBOL] = createContext(null);
|
|
170
|
+
}
|
|
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);
|
|
154
176
|
}
|
|
155
|
-
var
|
|
177
|
+
var WireFeaturesSettledContext = settledContextGlobal[SETTLED_SYMBOL];
|
|
156
178
|
var useWireFeaturesContext = () => useContext(WireFeaturesContext);
|
|
157
|
-
var
|
|
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 =
|
|
162
|
-
|
|
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);
|
|
@@ -213,9 +249,54 @@ var isCoachmarkTesting = () => coachmarkRuntime().testing;
|
|
|
213
249
|
var coachmarkGateKey = (tourId) => `wire_coachmark_${tourId}_seen`;
|
|
214
250
|
var showcaseGateKey = (showcaseId) => `wire_showcase_${showcaseId}_seen`;
|
|
215
251
|
var SEEN_VALUE = "1";
|
|
252
|
+
var GATE_PROBE_KEY = "wire_gate_probe";
|
|
253
|
+
var ASYNC_GATE_STORAGE_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
|
|
254
|
+
"@wireai/activation:asyncGateStorageWarned"
|
|
255
|
+
);
|
|
256
|
+
var asyncGateWarnGlobal = globalThis;
|
|
257
|
+
var closedGateStorage = Object.freeze({
|
|
258
|
+
getItem: () => SEEN_VALUE,
|
|
259
|
+
setItem: () => {
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
var syncGateAdapters = /* @__PURE__ */ new WeakSet();
|
|
263
|
+
var asyncGateAdapters = /* @__PURE__ */ new WeakSet();
|
|
264
|
+
var warnAsyncGateStorage = () => {
|
|
265
|
+
if (asyncGateWarnGlobal[ASYNC_GATE_STORAGE_WARNED_SLOT]) return;
|
|
266
|
+
if (typeof __DEV__ === "undefined" || !__DEV__) return;
|
|
267
|
+
if (typeof console === "undefined" || !console.warn) return;
|
|
268
|
+
asyncGateWarnGlobal[ASYNC_GATE_STORAGE_WARNED_SLOT] = true;
|
|
269
|
+
console.warn(
|
|
270
|
+
"[wireai] the gate storage's getItem returned a Promise \u2014 an async adapter (e.g. AsyncStorage) cannot drive the once-per-user gates, so coachmark tours, showcases and the review/questionnaire gates are all DISABLED (fail closed: nothing shows, nothing fires) until a synchronous adapter is provided. Pass a sync adapter (e.g. MMKV: `{ getItem: (k) => mmkv.getString(k) ?? null, setItem: (k, v) => mmkv.set(k, v) }`) to CoachmarkProvider or the gate's `storage` prop."
|
|
271
|
+
);
|
|
272
|
+
};
|
|
273
|
+
var validateGateStorage = (storage) => {
|
|
274
|
+
if (!storage) return null;
|
|
275
|
+
if (syncGateAdapters.has(storage)) return storage;
|
|
276
|
+
if (asyncGateAdapters.has(storage)) return closedGateStorage;
|
|
277
|
+
let probed;
|
|
278
|
+
try {
|
|
279
|
+
probed = storage.getItem(GATE_PROBE_KEY);
|
|
280
|
+
} catch {
|
|
281
|
+
syncGateAdapters.add(storage);
|
|
282
|
+
return storage;
|
|
283
|
+
}
|
|
284
|
+
if (probed != null && typeof probed.then === "function") {
|
|
285
|
+
asyncGateAdapters.add(storage);
|
|
286
|
+
try {
|
|
287
|
+
probed.then(void 0, () => {
|
|
288
|
+
});
|
|
289
|
+
} catch {
|
|
290
|
+
}
|
|
291
|
+
warnAsyncGateStorage();
|
|
292
|
+
return closedGateStorage;
|
|
293
|
+
}
|
|
294
|
+
syncGateAdapters.add(storage);
|
|
295
|
+
return storage;
|
|
296
|
+
};
|
|
216
297
|
var hasSeenGate = (key, storageOverride, testingOverride) => {
|
|
217
298
|
if (testingOverride != null ? testingOverride : isCoachmarkTesting()) return false;
|
|
218
|
-
const storage = storageOverride != null ? storageOverride : getCoachmarkStorage();
|
|
299
|
+
const storage = validateGateStorage(storageOverride != null ? storageOverride : getCoachmarkStorage());
|
|
219
300
|
if (!storage) return false;
|
|
220
301
|
try {
|
|
221
302
|
return storage.getItem(key) === SEEN_VALUE;
|
|
@@ -225,7 +306,7 @@ var hasSeenGate = (key, storageOverride, testingOverride) => {
|
|
|
225
306
|
};
|
|
226
307
|
var markSeenGate = (key, storageOverride, testingOverride) => {
|
|
227
308
|
if (testingOverride != null ? testingOverride : isCoachmarkTesting()) return;
|
|
228
|
-
const storage = storageOverride != null ? storageOverride : getCoachmarkStorage();
|
|
309
|
+
const storage = validateGateStorage(storageOverride != null ? storageOverride : getCoachmarkStorage());
|
|
229
310
|
if (!storage) return;
|
|
230
311
|
try {
|
|
231
312
|
storage.setItem(key, SEEN_VALUE);
|
|
@@ -962,22 +1043,28 @@ var CoachmarkProvider = ({
|
|
|
962
1043
|
featuresConfig,
|
|
963
1044
|
children
|
|
964
1045
|
}) => {
|
|
965
|
-
const
|
|
966
|
-
|
|
1046
|
+
const { flags, settled } = useResolvedFeaturesState({
|
|
1047
|
+
flags: features,
|
|
1048
|
+
config: featuresConfig
|
|
1049
|
+
});
|
|
1050
|
+
const coachmarksOn = settled ? flags.coachmarks.enabled : true;
|
|
967
1051
|
setCoachmarkStorage(storage);
|
|
968
1052
|
setCoachmarkTesting(isTestingCoachmark);
|
|
1053
|
+
setCoachmarksResolved(settled);
|
|
969
1054
|
setCoachmarksEnabled(coachmarksOn);
|
|
970
1055
|
useEffect(() => {
|
|
971
1056
|
setCoachmarkStorage(storage);
|
|
972
1057
|
setCoachmarkTesting(isTestingCoachmark);
|
|
1058
|
+
setCoachmarksResolved(settled);
|
|
973
1059
|
setCoachmarksEnabled(coachmarksOn);
|
|
974
1060
|
return () => {
|
|
975
1061
|
if (getCoachmarkStorage() === storage) {
|
|
976
1062
|
setCoachmarkStorage(null);
|
|
977
1063
|
}
|
|
978
1064
|
setCoachmarksEnabled(true);
|
|
1065
|
+
setCoachmarksResolved(true);
|
|
979
1066
|
};
|
|
980
|
-
}, [storage, isTestingCoachmark, coachmarksOn]);
|
|
1067
|
+
}, [storage, isTestingCoachmark, coachmarksOn, settled]);
|
|
981
1068
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
982
1069
|
children,
|
|
983
1070
|
/* @__PURE__ */ jsx(
|
|
@@ -1080,6 +1167,11 @@ var useCoachmarkTour = (steps, {
|
|
|
1080
1167
|
areCoachmarksEnabled,
|
|
1081
1168
|
areCoachmarksEnabled
|
|
1082
1169
|
);
|
|
1170
|
+
const coachmarksResolved = useSyncExternalStore(
|
|
1171
|
+
subscribeCoachmarksEnabled,
|
|
1172
|
+
areCoachmarksResolved,
|
|
1173
|
+
areCoachmarksResolved
|
|
1174
|
+
);
|
|
1083
1175
|
const exit = useCallback(
|
|
1084
1176
|
(writeGate) => {
|
|
1085
1177
|
if (finishedRef.current) return;
|
|
@@ -1094,14 +1186,16 @@ var useCoachmarkTour = (steps, {
|
|
|
1094
1186
|
const finish = useCallback(() => exit(true), [exit]);
|
|
1095
1187
|
useEffect(() => {
|
|
1096
1188
|
if (!enabled || startedRef.current) return void 0;
|
|
1189
|
+
if (steps.length === 0) return void 0;
|
|
1097
1190
|
if (!coachmarksOn) return void 0;
|
|
1191
|
+
if (!coachmarksResolved) return void 0;
|
|
1098
1192
|
if (showOnce && hasSeenGate(coachmarkGateKey(tourId))) return void 0;
|
|
1099
1193
|
const timer = setTimeout(() => {
|
|
1100
1194
|
startedRef.current = true;
|
|
1101
1195
|
setActiveIndex(0);
|
|
1102
1196
|
}, startDelayMs);
|
|
1103
1197
|
return () => clearTimeout(timer);
|
|
1104
|
-
}, [enabled, coachmarksOn, startDelayMs, showOnce, tourId]);
|
|
1198
|
+
}, [enabled, coachmarksOn, coachmarksResolved, startDelayMs, showOnce, tourId, steps.length]);
|
|
1105
1199
|
useEffect(() => {
|
|
1106
1200
|
if (activeIndex === null) return void 0;
|
|
1107
1201
|
if (!coachmarksOn) {
|
|
@@ -1112,6 +1206,10 @@ var useCoachmarkTour = (steps, {
|
|
|
1112
1206
|
if (startedRef.current && !finishedRef.current) coachmarkOverlay.hide();
|
|
1113
1207
|
return void 0;
|
|
1114
1208
|
}
|
|
1209
|
+
if (steps.length === 0) {
|
|
1210
|
+
exit(false);
|
|
1211
|
+
return void 0;
|
|
1212
|
+
}
|
|
1115
1213
|
if (activeIndex >= steps.length) {
|
|
1116
1214
|
finish();
|
|
1117
1215
|
return void 0;
|