@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React8, { createContext, forwardRef, useRef, useState, useEffect, useCallback, useImperativeHandle, useMemo, useContext } from 'react';
|
|
1
|
+
import React8, { createContext, forwardRef, useRef, useState, useEffect, useCallback, useImperativeHandle, useMemo, useContext, useSyncExternalStore } from 'react';
|
|
2
2
|
import { StyleSheet, Animated, Easing, BackHandler, Modal, Pressable, Text, AccessibilityInfo, View, TextInput } from 'react-native';
|
|
3
3
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
@@ -837,6 +837,28 @@ var toAnswers = (drafts) => {
|
|
|
837
837
|
};
|
|
838
838
|
};
|
|
839
839
|
|
|
840
|
+
// src/utils/withDeadline.ts
|
|
841
|
+
var DEADLINE_EXPIRED = /* @__PURE__ */ Symbol("wire-deadline-expired");
|
|
842
|
+
var DEFAULT_DEADLINE_MS = 15e3;
|
|
843
|
+
var withDeadline = async (exchange, timeoutMs = DEFAULT_DEADLINE_MS) => {
|
|
844
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
845
|
+
let timer;
|
|
846
|
+
const expired = new Promise((resolve) => {
|
|
847
|
+
timer = setTimeout(() => {
|
|
848
|
+
controller == null ? void 0 : controller.abort();
|
|
849
|
+
resolve(DEADLINE_EXPIRED);
|
|
850
|
+
}, timeoutMs);
|
|
851
|
+
});
|
|
852
|
+
const running = exchange(controller == null ? void 0 : controller.signal);
|
|
853
|
+
try {
|
|
854
|
+
return await Promise.race([running, expired]);
|
|
855
|
+
} finally {
|
|
856
|
+
clearTimeout(timer);
|
|
857
|
+
void running.catch(() => {
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
|
|
840
862
|
// src/questionnaire/transport.ts
|
|
841
863
|
var submitQuestionnaireResponse = async (target, id, submission) => {
|
|
842
864
|
if (!(target == null ? void 0 : target.serverUrl) || !id) return "unsent";
|
|
@@ -845,11 +867,15 @@ var submitQuestionnaireResponse = async (target, id, submission) => {
|
|
|
845
867
|
const url = `${base}/v1/questionnaires/${encodeURIComponent(id)}/responses`;
|
|
846
868
|
const headers = { "Content-Type": "application/json" };
|
|
847
869
|
if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
|
|
848
|
-
const res = await
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
870
|
+
const res = await withDeadline(
|
|
871
|
+
(signal) => fetch(url, {
|
|
872
|
+
method: "POST",
|
|
873
|
+
headers,
|
|
874
|
+
body: JSON.stringify(submission),
|
|
875
|
+
signal
|
|
876
|
+
})
|
|
877
|
+
);
|
|
878
|
+
if (res === DEADLINE_EXPIRED) return "unsent";
|
|
853
879
|
if (!res) return "unsent";
|
|
854
880
|
return res.ok ? "accepted" : "rejected";
|
|
855
881
|
} catch {
|
|
@@ -867,9 +893,12 @@ var fetchQuestionnaireDecision = async (target, options = {}) => {
|
|
|
867
893
|
const url = `${base}/v1/questionnaires/decision${qs ? `?${qs}` : ""}`;
|
|
868
894
|
const headers = {};
|
|
869
895
|
if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
|
|
870
|
-
const
|
|
871
|
-
|
|
872
|
-
|
|
896
|
+
const json = await withDeadline(async (signal) => {
|
|
897
|
+
const res = await fetch(url, { headers, signal });
|
|
898
|
+
if (!res || !res.ok) return null;
|
|
899
|
+
return await res.json();
|
|
900
|
+
});
|
|
901
|
+
if (json === DEADLINE_EXPIRED) return null;
|
|
873
902
|
if (!json || typeof json.fire !== "boolean") return null;
|
|
874
903
|
return json;
|
|
875
904
|
} catch {
|
|
@@ -948,6 +977,10 @@ var _QuestionnaireGate = ({
|
|
|
948
977
|
}, [id, onResolved]);
|
|
949
978
|
const postedRef = useRef(false);
|
|
950
979
|
const unackedRef = useRef(null);
|
|
980
|
+
const recoverOnSettleRef = useRef(false);
|
|
981
|
+
const recoveredRef = useRef(false);
|
|
982
|
+
const postOnceRef = useRef(() => {
|
|
983
|
+
});
|
|
951
984
|
const postOnce = useCallback(
|
|
952
985
|
(body) => {
|
|
953
986
|
if (postedRef.current) return;
|
|
@@ -956,6 +989,10 @@ var _QuestionnaireGate = ({
|
|
|
956
989
|
void submitQuestionnaireResponse(target, id, body).then((result) => {
|
|
957
990
|
if (result === "unsent") {
|
|
958
991
|
postedRef.current = false;
|
|
992
|
+
if (recoverOnSettleRef.current && !recoveredRef.current) {
|
|
993
|
+
recoveredRef.current = true;
|
|
994
|
+
postOnceRef.current(body);
|
|
995
|
+
}
|
|
959
996
|
return;
|
|
960
997
|
}
|
|
961
998
|
unackedRef.current = null;
|
|
@@ -963,11 +1000,16 @@ var _QuestionnaireGate = ({
|
|
|
963
1000
|
},
|
|
964
1001
|
[target, id]
|
|
965
1002
|
);
|
|
1003
|
+
postOnceRef.current = postOnce;
|
|
966
1004
|
const recoverRef = useRef(() => {
|
|
967
1005
|
});
|
|
968
1006
|
recoverRef.current = () => {
|
|
969
1007
|
const undelivered = unackedRef.current;
|
|
970
|
-
if (undelivered && !postedRef.current)
|
|
1008
|
+
if (undelivered && !postedRef.current) {
|
|
1009
|
+
postOnce(undelivered);
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
if (postedRef.current && undelivered) recoverOnSettleRef.current = true;
|
|
971
1013
|
};
|
|
972
1014
|
React8.useEffect(() => () => recoverRef.current(), []);
|
|
973
1015
|
const finish = useCallback(
|
|
@@ -1116,6 +1158,39 @@ var styles7 = StyleSheet.create({
|
|
|
1116
1158
|
backGlyph: { includeFontPadding: false }
|
|
1117
1159
|
});
|
|
1118
1160
|
|
|
1161
|
+
// src/analytics/reportClientEvent.ts
|
|
1162
|
+
var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
1163
|
+
|
|
1164
|
+
// src/analytics/currentSession.ts
|
|
1165
|
+
var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
|
|
1166
|
+
"@wireai/activation:currentSessionId"
|
|
1167
|
+
);
|
|
1168
|
+
var globalSlot = globalThis;
|
|
1169
|
+
var SESSION_ID_LISTENERS_SLOT = /* @__PURE__ */ Symbol.for(
|
|
1170
|
+
"@wireai/activation:currentSessionIdListeners"
|
|
1171
|
+
);
|
|
1172
|
+
var listenerSlot = globalThis;
|
|
1173
|
+
var listeners = () => {
|
|
1174
|
+
const existing = listenerSlot[SESSION_ID_LISTENERS_SLOT];
|
|
1175
|
+
if (existing) return existing;
|
|
1176
|
+
const created = /* @__PURE__ */ new Set();
|
|
1177
|
+
listenerSlot[SESSION_ID_LISTENERS_SLOT] = created;
|
|
1178
|
+
return created;
|
|
1179
|
+
};
|
|
1180
|
+
var subscribeCurrentSessionId = (listener) => {
|
|
1181
|
+
const set = listeners();
|
|
1182
|
+
set.add(listener);
|
|
1183
|
+
return () => {
|
|
1184
|
+
set.delete(listener);
|
|
1185
|
+
};
|
|
1186
|
+
};
|
|
1187
|
+
var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
|
|
1188
|
+
var MINTED_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
|
|
1189
|
+
"@wireai/activation:mintedSessionId"
|
|
1190
|
+
);
|
|
1191
|
+
var mintedSlot = globalThis;
|
|
1192
|
+
var isMintedSessionId = (id) => id !== void 0 && id === mintedSlot[MINTED_SESSION_ID_SLOT];
|
|
1193
|
+
|
|
1119
1194
|
// src/coachmarks/runtime.ts
|
|
1120
1195
|
var COACHMARK_RUNTIME_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:coachmarkRuntime");
|
|
1121
1196
|
var runtimeGlobal = globalThis;
|
|
@@ -1129,9 +1204,54 @@ var coachmarkRuntime = () => {
|
|
|
1129
1204
|
var getCoachmarkStorage = () => coachmarkRuntime().storage;
|
|
1130
1205
|
var isCoachmarkTesting = () => coachmarkRuntime().testing;
|
|
1131
1206
|
var SEEN_VALUE = "1";
|
|
1207
|
+
var GATE_PROBE_KEY = "wire_gate_probe";
|
|
1208
|
+
var ASYNC_GATE_STORAGE_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
|
|
1209
|
+
"@wireai/activation:asyncGateStorageWarned"
|
|
1210
|
+
);
|
|
1211
|
+
var asyncGateWarnGlobal = globalThis;
|
|
1212
|
+
var closedGateStorage = Object.freeze({
|
|
1213
|
+
getItem: () => SEEN_VALUE,
|
|
1214
|
+
setItem: () => {
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1217
|
+
var syncGateAdapters = /* @__PURE__ */ new WeakSet();
|
|
1218
|
+
var asyncGateAdapters = /* @__PURE__ */ new WeakSet();
|
|
1219
|
+
var warnAsyncGateStorage = () => {
|
|
1220
|
+
if (asyncGateWarnGlobal[ASYNC_GATE_STORAGE_WARNED_SLOT]) return;
|
|
1221
|
+
if (typeof __DEV__ === "undefined" || !__DEV__) return;
|
|
1222
|
+
if (typeof console === "undefined" || !console.warn) return;
|
|
1223
|
+
asyncGateWarnGlobal[ASYNC_GATE_STORAGE_WARNED_SLOT] = true;
|
|
1224
|
+
console.warn(
|
|
1225
|
+
"[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."
|
|
1226
|
+
);
|
|
1227
|
+
};
|
|
1228
|
+
var validateGateStorage = (storage) => {
|
|
1229
|
+
if (!storage) return null;
|
|
1230
|
+
if (syncGateAdapters.has(storage)) return storage;
|
|
1231
|
+
if (asyncGateAdapters.has(storage)) return closedGateStorage;
|
|
1232
|
+
let probed;
|
|
1233
|
+
try {
|
|
1234
|
+
probed = storage.getItem(GATE_PROBE_KEY);
|
|
1235
|
+
} catch {
|
|
1236
|
+
syncGateAdapters.add(storage);
|
|
1237
|
+
return storage;
|
|
1238
|
+
}
|
|
1239
|
+
if (probed != null && typeof probed.then === "function") {
|
|
1240
|
+
asyncGateAdapters.add(storage);
|
|
1241
|
+
try {
|
|
1242
|
+
probed.then(void 0, () => {
|
|
1243
|
+
});
|
|
1244
|
+
} catch {
|
|
1245
|
+
}
|
|
1246
|
+
warnAsyncGateStorage();
|
|
1247
|
+
return closedGateStorage;
|
|
1248
|
+
}
|
|
1249
|
+
syncGateAdapters.add(storage);
|
|
1250
|
+
return storage;
|
|
1251
|
+
};
|
|
1132
1252
|
var hasSeenGate = (key, storageOverride, testingOverride) => {
|
|
1133
1253
|
if (testingOverride != null ? testingOverride : isCoachmarkTesting()) return false;
|
|
1134
|
-
const storage = storageOverride != null ? storageOverride : getCoachmarkStorage();
|
|
1254
|
+
const storage = validateGateStorage(storageOverride != null ? storageOverride : getCoachmarkStorage());
|
|
1135
1255
|
if (!storage) return false;
|
|
1136
1256
|
try {
|
|
1137
1257
|
return storage.getItem(key) === SEEN_VALUE;
|
|
@@ -1141,7 +1261,7 @@ var hasSeenGate = (key, storageOverride, testingOverride) => {
|
|
|
1141
1261
|
};
|
|
1142
1262
|
var markSeenGate = (key, storageOverride, testingOverride) => {
|
|
1143
1263
|
if (testingOverride != null ? testingOverride : isCoachmarkTesting()) return;
|
|
1144
|
-
const storage = storageOverride != null ? storageOverride : getCoachmarkStorage();
|
|
1264
|
+
const storage = validateGateStorage(storageOverride != null ? storageOverride : getCoachmarkStorage());
|
|
1145
1265
|
if (!storage) return;
|
|
1146
1266
|
try {
|
|
1147
1267
|
storage.setItem(key, SEEN_VALUE);
|
|
@@ -1229,17 +1349,31 @@ var failOpen = async (storage, key) => {
|
|
|
1229
1349
|
const cached = await readCachedFeatures(storage, key);
|
|
1230
1350
|
return (_a = cached == null ? void 0 : cached.features) != null ? _a : defaultWireFeatures;
|
|
1231
1351
|
};
|
|
1232
|
-
var
|
|
1352
|
+
var NO_ANSWER = /* @__PURE__ */ Symbol("wire-features-no-answer");
|
|
1353
|
+
var fetchFeaturesJson = async (url, apiKey, timeoutMs) => {
|
|
1233
1354
|
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1355
|
+
let timer;
|
|
1356
|
+
const expired = new Promise((resolve) => {
|
|
1357
|
+
timer = setTimeout(() => {
|
|
1358
|
+
controller == null ? void 0 : controller.abort();
|
|
1359
|
+
resolve(NO_ANSWER);
|
|
1360
|
+
}, timeoutMs);
|
|
1361
|
+
});
|
|
1362
|
+
const exchange = (async () => {
|
|
1363
|
+
const res = await fetch(url, {
|
|
1237
1364
|
method: "GET",
|
|
1238
1365
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
1239
1366
|
signal: controller == null ? void 0 : controller.signal
|
|
1240
1367
|
});
|
|
1368
|
+
if (!res || !res.ok) return NO_ANSWER;
|
|
1369
|
+
return await res.json();
|
|
1370
|
+
})();
|
|
1371
|
+
try {
|
|
1372
|
+
return await Promise.race([exchange, expired]);
|
|
1241
1373
|
} finally {
|
|
1242
1374
|
clearTimeout(timer);
|
|
1375
|
+
void exchange.catch(() => {
|
|
1376
|
+
});
|
|
1243
1377
|
}
|
|
1244
1378
|
};
|
|
1245
1379
|
var fetchWireFeatures = async (config) => {
|
|
@@ -1249,9 +1383,8 @@ var fetchWireFeatures = async (config) => {
|
|
|
1249
1383
|
const url = `${config.serverUrl.replace(/\/$/, "")}/v1/features`;
|
|
1250
1384
|
const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
1251
1385
|
try {
|
|
1252
|
-
const
|
|
1253
|
-
if (
|
|
1254
|
-
const json = await res.json();
|
|
1386
|
+
const json = await fetchFeaturesJson(url, config.apiKey, timeoutMs);
|
|
1387
|
+
if (json === NO_ANSWER) return failOpen(storage, key);
|
|
1255
1388
|
const features = parseWireFeatures(json);
|
|
1256
1389
|
if (storage) writeCachedFeatures(storage, key, features);
|
|
1257
1390
|
return features;
|
|
@@ -1267,39 +1400,51 @@ var useStableValue = (value, isEqual) => {
|
|
|
1267
1400
|
return ref.current;
|
|
1268
1401
|
};
|
|
1269
1402
|
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);
|
|
1270
|
-
var
|
|
1403
|
+
var useWireFeaturesState = (config) => {
|
|
1271
1404
|
const stableConfig = useStableValue(config, sameFetchKey);
|
|
1272
1405
|
const [flags, setFlags] = useState(defaultWireFeatures);
|
|
1273
1406
|
const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
|
|
1407
|
+
const [settled, setSettled] = useState(!canFetch);
|
|
1274
1408
|
useEffect(() => {
|
|
1275
1409
|
if (!canFetch) {
|
|
1276
1410
|
setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
|
|
1411
|
+
setSettled(true);
|
|
1277
1412
|
return;
|
|
1278
1413
|
}
|
|
1279
1414
|
let alive = true;
|
|
1280
1415
|
void fetchWireFeatures(stableConfig).then((next) => {
|
|
1281
1416
|
if (!alive) return;
|
|
1282
1417
|
setFlags((prev) => featuresEqual(prev, next) ? prev : next);
|
|
1418
|
+
setSettled(true);
|
|
1283
1419
|
});
|
|
1284
1420
|
return () => {
|
|
1285
1421
|
alive = false;
|
|
1286
1422
|
};
|
|
1287
1423
|
}, [canFetch, stableConfig]);
|
|
1288
|
-
return flags;
|
|
1424
|
+
return useMemo(() => ({ flags, settled }), [flags, settled]);
|
|
1289
1425
|
};
|
|
1290
1426
|
var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
|
|
1291
|
-
var
|
|
1292
|
-
if (!
|
|
1293
|
-
|
|
1427
|
+
var featuresContextGlobal = globalThis;
|
|
1428
|
+
if (!featuresContextGlobal[CONTEXT_SYMBOL]) {
|
|
1429
|
+
featuresContextGlobal[CONTEXT_SYMBOL] = createContext(null);
|
|
1430
|
+
}
|
|
1431
|
+
var WireFeaturesContext = featuresContextGlobal[CONTEXT_SYMBOL];
|
|
1432
|
+
var SETTLED_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.settled.context");
|
|
1433
|
+
var settledContextGlobal = globalThis;
|
|
1434
|
+
if (!settledContextGlobal[SETTLED_SYMBOL]) {
|
|
1435
|
+
settledContextGlobal[SETTLED_SYMBOL] = createContext(true);
|
|
1294
1436
|
}
|
|
1295
|
-
var
|
|
1437
|
+
var WireFeaturesSettledContext = settledContextGlobal[SETTLED_SYMBOL];
|
|
1296
1438
|
var useWireFeaturesContext = () => useContext(WireFeaturesContext);
|
|
1297
|
-
var
|
|
1439
|
+
var useResolvedFeaturesState = (options) => {
|
|
1298
1440
|
var _a, _b, _c;
|
|
1299
1441
|
const ctx = useWireFeaturesContext();
|
|
1442
|
+
const ctxSettled = useContext(WireFeaturesSettledContext);
|
|
1300
1443
|
const shouldFetch = !(options == null ? void 0 : options.flags) && ctx == null;
|
|
1301
|
-
const fetched =
|
|
1302
|
-
|
|
1444
|
+
const fetched = useWireFeaturesState(shouldFetch ? options == null ? void 0 : options.config : void 0);
|
|
1445
|
+
const flags = (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched.flags) != null ? _c : defaultWireFeatures;
|
|
1446
|
+
const settled = (options == null ? void 0 : options.flags) ? true : ctx != null ? ctxSettled : fetched.settled;
|
|
1447
|
+
return useMemo(() => ({ flags, settled }), [flags, settled]);
|
|
1303
1448
|
};
|
|
1304
1449
|
|
|
1305
1450
|
// src/reviews/equality.ts
|
|
@@ -1323,18 +1468,8 @@ var sameDecision = (a, b) => {
|
|
|
1323
1468
|
return a.fire === b.fire && a.reason === b.reason;
|
|
1324
1469
|
};
|
|
1325
1470
|
|
|
1326
|
-
// src/analytics/reportClientEvent.ts
|
|
1327
|
-
var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
1328
|
-
|
|
1329
|
-
// src/analytics/currentSession.ts
|
|
1330
|
-
var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
|
|
1331
|
-
"@wireai/activation:currentSessionId"
|
|
1332
|
-
);
|
|
1333
|
-
var globalSlot = globalThis;
|
|
1334
|
-
var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
|
|
1335
|
-
|
|
1336
1471
|
// src/reviews/runtime.ts
|
|
1337
|
-
var resolveStorage = (override) => override != null ? override : getCoachmarkStorage();
|
|
1472
|
+
var resolveStorage = (override) => validateGateStorage(override != null ? override : getCoachmarkStorage());
|
|
1338
1473
|
var NO_STORAGE_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
|
|
1339
1474
|
"@wireai/activation:gateStorageWarned"
|
|
1340
1475
|
);
|
|
@@ -1379,18 +1514,23 @@ var PROCESS_OPEN_ID_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:proces
|
|
|
1379
1514
|
var openIdGlobal = globalThis;
|
|
1380
1515
|
var currentOpenId = () => {
|
|
1381
1516
|
const live = getCurrentSessionId();
|
|
1517
|
+
const liveMinted = isMintedSessionId(live);
|
|
1382
1518
|
const pin = openIdGlobal[PROCESS_OPEN_ID_SLOT];
|
|
1383
1519
|
if (!pin) {
|
|
1384
1520
|
const openId = live != null ? live : makeSessionId();
|
|
1385
|
-
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId, observed: live };
|
|
1521
|
+
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId, observed: live, observedMinted: liveMinted };
|
|
1386
1522
|
return openId;
|
|
1387
1523
|
}
|
|
1388
1524
|
if (!live || live === pin.observed) return pin.openId;
|
|
1389
|
-
if (pin.observed === void 0) {
|
|
1390
|
-
openIdGlobal[PROCESS_OPEN_ID_SLOT] = {
|
|
1525
|
+
if (pin.observed === void 0 || pin.observedMinted) {
|
|
1526
|
+
openIdGlobal[PROCESS_OPEN_ID_SLOT] = {
|
|
1527
|
+
openId: pin.openId,
|
|
1528
|
+
observed: live,
|
|
1529
|
+
observedMinted: liveMinted
|
|
1530
|
+
};
|
|
1391
1531
|
return pin.openId;
|
|
1392
1532
|
}
|
|
1393
|
-
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId: live, observed: live };
|
|
1533
|
+
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId: live, observed: live, observedMinted: liveMinted };
|
|
1394
1534
|
return live;
|
|
1395
1535
|
};
|
|
1396
1536
|
var bumpSessionCount = (storage, sessionsKey, openKey, openId = currentOpenId()) => {
|
|
@@ -1431,7 +1571,11 @@ var useQuestionnaireGate = ({
|
|
|
1431
1571
|
}) => {
|
|
1432
1572
|
const config = useStableValue2(configProp, shallowEqual);
|
|
1433
1573
|
const decision = useStableValue2(decisionProp, (a, b) => sameDecision(a, b));
|
|
1434
|
-
const
|
|
1574
|
+
const { flags: resolvedFeatures, settled: featuresSettled } = useResolvedFeaturesState({
|
|
1575
|
+
flags: features,
|
|
1576
|
+
config: featuresConfig
|
|
1577
|
+
});
|
|
1578
|
+
const featureEnabled = resolvedFeatures.questionnaire.enabled;
|
|
1435
1579
|
const testing = isTesting != null ? isTesting : isCoachmarkTesting();
|
|
1436
1580
|
const seenKey = questionnaireSeenKey(
|
|
1437
1581
|
config.id,
|
|
@@ -1440,11 +1584,22 @@ var useQuestionnaireGate = ({
|
|
|
1440
1584
|
const lastKey = questionnaireLastShownKey(config.id);
|
|
1441
1585
|
const sessionsKey = questionnaireSessionsKey(config.id);
|
|
1442
1586
|
const sessionOpenKey = questionnaireSessionOpenKey(config.id);
|
|
1443
|
-
const
|
|
1587
|
+
const openSessionId = useSyncExternalStore(
|
|
1588
|
+
subscribeCurrentSessionId,
|
|
1589
|
+
getCurrentSessionId,
|
|
1590
|
+
getCurrentSessionId
|
|
1591
|
+
);
|
|
1592
|
+
const storageRef = useRef(storage);
|
|
1593
|
+
storageRef.current = storage;
|
|
1594
|
+
const [sessions, setSessions] = useState(() => {
|
|
1444
1595
|
const store = resolveStorage(storage);
|
|
1445
1596
|
warnMissingGateStorage(store, "questionnaire");
|
|
1446
1597
|
return bumpSessionCount(store, sessionsKey, sessionOpenKey);
|
|
1447
|
-
})
|
|
1598
|
+
});
|
|
1599
|
+
useEffect(() => {
|
|
1600
|
+
const next = bumpSessionCount(resolveStorage(storageRef.current), sessionsKey, sessionOpenKey);
|
|
1601
|
+
setSessions((prev) => prev === next ? prev : next);
|
|
1602
|
+
}, [openSessionId, sessionsKey, sessionOpenKey]);
|
|
1448
1603
|
const hasServerDecision = decision != null;
|
|
1449
1604
|
const timeoutMs = config.timeoutFallbackMs;
|
|
1450
1605
|
const waits = !hasServerDecision && timeoutMs != null && timeoutMs > 0;
|
|
@@ -1478,10 +1633,11 @@ var useQuestionnaireGate = ({
|
|
|
1478
1633
|
});
|
|
1479
1634
|
const resolved = decideQuestionnaire(local, decision);
|
|
1480
1635
|
const alreadySeen = hasSeenGate(seenKey, storage, isTesting);
|
|
1481
|
-
const ready = hasServerDecision || elapsed;
|
|
1636
|
+
const ready = (hasServerDecision || elapsed) && featuresSettled;
|
|
1482
1637
|
return { visible: ready && resolved.fire && !alreadySeen, verdict: resolved };
|
|
1483
1638
|
}, [
|
|
1484
1639
|
featureEnabled,
|
|
1640
|
+
featuresSettled,
|
|
1485
1641
|
testing,
|
|
1486
1642
|
config,
|
|
1487
1643
|
decision,
|