@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.
- package/AGENTS.md +95 -20
- package/CHANGELOG.md +707 -0
- package/INTEGRATION_PROMPT.md +61 -23
- package/README.md +100 -25
- package/dist/analytics/index.d.mts +32 -10
- package/dist/analytics/index.d.ts +32 -10
- 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 +14 -0
- package/dist/coachmarks/index.d.ts +14 -0
- package/dist/coachmarks/index.js +73 -20
- package/dist/coachmarks/index.js.map +1 -1
- package/dist/coachmarks/index.mjs +73 -20
- package/dist/coachmarks/index.mjs.map +1 -1
- package/dist/{currentSession-orZy5p1e.d.mts → currentSession-Bz7G6lno.d.mts} +25 -35
- package/dist/{currentSession-CFSRZ2wg.d.ts → currentSession-z-CZ55ad.d.ts} +25 -35
- 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 +0 -13
- package/dist/questionnaire/index.d.ts +0 -13
- package/dist/questionnaire/index.js +154 -43
- package/dist/questionnaire/index.js.map +1 -1
- package/dist/questionnaire/index.mjs +155 -44
- package/dist/questionnaire/index.mjs.map +1 -1
- package/dist/reviews/index.js +159 -91
- package/dist/reviews/index.js.map +1 -1
- package/dist/reviews/index.mjs +160 -92
- package/dist/reviews/index.mjs.map +1 -1
- package/dist/showcase/index.js +59 -18
- package/dist/showcase/index.js.map +1 -1
- package/dist/showcase/index.mjs +60 -19
- package/dist/showcase/index.mjs.map +1 -1
- package/llms.txt +9 -9
- package/package.json +6 -9
- 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 +53 -0
- package/src/coachmarks/useCoachmarkTour.ts +51 -1
- package/src/context/deviceId.ts +49 -15
- 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 +39 -0
- package/src/reviews/idempotency.ts +38 -0
- package/src/reviews/runtime.ts +39 -10
- package/src/reviews/transport.ts +22 -8
- 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 +5 -4
- 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;
|
|
@@ -1274,17 +1349,31 @@ var failOpen = async (storage, key) => {
|
|
|
1274
1349
|
const cached = await readCachedFeatures(storage, key);
|
|
1275
1350
|
return (_a = cached == null ? void 0 : cached.features) != null ? _a : defaultWireFeatures;
|
|
1276
1351
|
};
|
|
1277
|
-
var
|
|
1352
|
+
var NO_ANSWER = /* @__PURE__ */ Symbol("wire-features-no-answer");
|
|
1353
|
+
var fetchFeaturesJson = async (url, apiKey, timeoutMs) => {
|
|
1278
1354
|
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
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, {
|
|
1282
1364
|
method: "GET",
|
|
1283
1365
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
1284
1366
|
signal: controller == null ? void 0 : controller.signal
|
|
1285
1367
|
});
|
|
1368
|
+
if (!res || !res.ok) return NO_ANSWER;
|
|
1369
|
+
return await res.json();
|
|
1370
|
+
})();
|
|
1371
|
+
try {
|
|
1372
|
+
return await Promise.race([exchange, expired]);
|
|
1286
1373
|
} finally {
|
|
1287
1374
|
clearTimeout(timer);
|
|
1375
|
+
void exchange.catch(() => {
|
|
1376
|
+
});
|
|
1288
1377
|
}
|
|
1289
1378
|
};
|
|
1290
1379
|
var fetchWireFeatures = async (config) => {
|
|
@@ -1294,9 +1383,8 @@ var fetchWireFeatures = async (config) => {
|
|
|
1294
1383
|
const url = `${config.serverUrl.replace(/\/$/, "")}/v1/features`;
|
|
1295
1384
|
const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
1296
1385
|
try {
|
|
1297
|
-
const
|
|
1298
|
-
if (
|
|
1299
|
-
const json = await res.json();
|
|
1386
|
+
const json = await fetchFeaturesJson(url, config.apiKey, timeoutMs);
|
|
1387
|
+
if (json === NO_ANSWER) return failOpen(storage, key);
|
|
1300
1388
|
const features = parseWireFeatures(json);
|
|
1301
1389
|
if (storage) writeCachedFeatures(storage, key, features);
|
|
1302
1390
|
return features;
|
|
@@ -1312,39 +1400,51 @@ var useStableValue = (value, isEqual) => {
|
|
|
1312
1400
|
return ref.current;
|
|
1313
1401
|
};
|
|
1314
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);
|
|
1315
|
-
var
|
|
1403
|
+
var useWireFeaturesState = (config) => {
|
|
1316
1404
|
const stableConfig = useStableValue(config, sameFetchKey);
|
|
1317
1405
|
const [flags, setFlags] = useState(defaultWireFeatures);
|
|
1318
1406
|
const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
|
|
1407
|
+
const [settled, setSettled] = useState(!canFetch);
|
|
1319
1408
|
useEffect(() => {
|
|
1320
1409
|
if (!canFetch) {
|
|
1321
1410
|
setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
|
|
1411
|
+
setSettled(true);
|
|
1322
1412
|
return;
|
|
1323
1413
|
}
|
|
1324
1414
|
let alive = true;
|
|
1325
1415
|
void fetchWireFeatures(stableConfig).then((next) => {
|
|
1326
1416
|
if (!alive) return;
|
|
1327
1417
|
setFlags((prev) => featuresEqual(prev, next) ? prev : next);
|
|
1418
|
+
setSettled(true);
|
|
1328
1419
|
});
|
|
1329
1420
|
return () => {
|
|
1330
1421
|
alive = false;
|
|
1331
1422
|
};
|
|
1332
1423
|
}, [canFetch, stableConfig]);
|
|
1333
|
-
return flags;
|
|
1424
|
+
return useMemo(() => ({ flags, settled }), [flags, settled]);
|
|
1334
1425
|
};
|
|
1335
1426
|
var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
|
|
1336
|
-
var
|
|
1337
|
-
if (!
|
|
1338
|
-
|
|
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);
|
|
1339
1436
|
}
|
|
1340
|
-
var
|
|
1437
|
+
var WireFeaturesSettledContext = settledContextGlobal[SETTLED_SYMBOL];
|
|
1341
1438
|
var useWireFeaturesContext = () => useContext(WireFeaturesContext);
|
|
1342
|
-
var
|
|
1439
|
+
var useResolvedFeaturesState = (options) => {
|
|
1343
1440
|
var _a, _b, _c;
|
|
1344
1441
|
const ctx = useWireFeaturesContext();
|
|
1442
|
+
const ctxSettled = useContext(WireFeaturesSettledContext);
|
|
1345
1443
|
const shouldFetch = !(options == null ? void 0 : options.flags) && ctx == null;
|
|
1346
|
-
const fetched =
|
|
1347
|
-
|
|
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]);
|
|
1348
1448
|
};
|
|
1349
1449
|
|
|
1350
1450
|
// src/reviews/equality.ts
|
|
@@ -1368,16 +1468,6 @@ var sameDecision = (a, b) => {
|
|
|
1368
1468
|
return a.fire === b.fire && a.reason === b.reason;
|
|
1369
1469
|
};
|
|
1370
1470
|
|
|
1371
|
-
// src/analytics/reportClientEvent.ts
|
|
1372
|
-
var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
1373
|
-
|
|
1374
|
-
// src/analytics/currentSession.ts
|
|
1375
|
-
var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
|
|
1376
|
-
"@wireai/activation:currentSessionId"
|
|
1377
|
-
);
|
|
1378
|
-
var globalSlot = globalThis;
|
|
1379
|
-
var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
|
|
1380
|
-
|
|
1381
1471
|
// src/reviews/runtime.ts
|
|
1382
1472
|
var resolveStorage = (override) => validateGateStorage(override != null ? override : getCoachmarkStorage());
|
|
1383
1473
|
var NO_STORAGE_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
|
|
@@ -1424,18 +1514,23 @@ var PROCESS_OPEN_ID_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:proces
|
|
|
1424
1514
|
var openIdGlobal = globalThis;
|
|
1425
1515
|
var currentOpenId = () => {
|
|
1426
1516
|
const live = getCurrentSessionId();
|
|
1517
|
+
const liveMinted = isMintedSessionId(live);
|
|
1427
1518
|
const pin = openIdGlobal[PROCESS_OPEN_ID_SLOT];
|
|
1428
1519
|
if (!pin) {
|
|
1429
1520
|
const openId = live != null ? live : makeSessionId();
|
|
1430
|
-
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId, observed: live };
|
|
1521
|
+
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId, observed: live, observedMinted: liveMinted };
|
|
1431
1522
|
return openId;
|
|
1432
1523
|
}
|
|
1433
1524
|
if (!live || live === pin.observed) return pin.openId;
|
|
1434
|
-
if (pin.observed === void 0) {
|
|
1435
|
-
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
|
+
};
|
|
1436
1531
|
return pin.openId;
|
|
1437
1532
|
}
|
|
1438
|
-
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId: live, observed: live };
|
|
1533
|
+
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId: live, observed: live, observedMinted: liveMinted };
|
|
1439
1534
|
return live;
|
|
1440
1535
|
};
|
|
1441
1536
|
var bumpSessionCount = (storage, sessionsKey, openKey, openId = currentOpenId()) => {
|
|
@@ -1476,7 +1571,11 @@ var useQuestionnaireGate = ({
|
|
|
1476
1571
|
}) => {
|
|
1477
1572
|
const config = useStableValue2(configProp, shallowEqual);
|
|
1478
1573
|
const decision = useStableValue2(decisionProp, (a, b) => sameDecision(a, b));
|
|
1479
|
-
const
|
|
1574
|
+
const { flags: resolvedFeatures, settled: featuresSettled } = useResolvedFeaturesState({
|
|
1575
|
+
flags: features,
|
|
1576
|
+
config: featuresConfig
|
|
1577
|
+
});
|
|
1578
|
+
const featureEnabled = resolvedFeatures.questionnaire.enabled;
|
|
1480
1579
|
const testing = isTesting != null ? isTesting : isCoachmarkTesting();
|
|
1481
1580
|
const seenKey = questionnaireSeenKey(
|
|
1482
1581
|
config.id,
|
|
@@ -1485,11 +1584,22 @@ var useQuestionnaireGate = ({
|
|
|
1485
1584
|
const lastKey = questionnaireLastShownKey(config.id);
|
|
1486
1585
|
const sessionsKey = questionnaireSessionsKey(config.id);
|
|
1487
1586
|
const sessionOpenKey = questionnaireSessionOpenKey(config.id);
|
|
1488
|
-
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(() => {
|
|
1489
1595
|
const store = resolveStorage(storage);
|
|
1490
1596
|
warnMissingGateStorage(store, "questionnaire");
|
|
1491
1597
|
return bumpSessionCount(store, sessionsKey, sessionOpenKey);
|
|
1492
|
-
})
|
|
1598
|
+
});
|
|
1599
|
+
useEffect(() => {
|
|
1600
|
+
const next = bumpSessionCount(resolveStorage(storageRef.current), sessionsKey, sessionOpenKey);
|
|
1601
|
+
setSessions((prev) => prev === next ? prev : next);
|
|
1602
|
+
}, [openSessionId, sessionsKey, sessionOpenKey]);
|
|
1493
1603
|
const hasServerDecision = decision != null;
|
|
1494
1604
|
const timeoutMs = config.timeoutFallbackMs;
|
|
1495
1605
|
const waits = !hasServerDecision && timeoutMs != null && timeoutMs > 0;
|
|
@@ -1523,10 +1633,11 @@ var useQuestionnaireGate = ({
|
|
|
1523
1633
|
});
|
|
1524
1634
|
const resolved = decideQuestionnaire(local, decision);
|
|
1525
1635
|
const alreadySeen = hasSeenGate(seenKey, storage, isTesting);
|
|
1526
|
-
const ready = hasServerDecision || elapsed;
|
|
1636
|
+
const ready = (hasServerDecision || elapsed) && featuresSettled;
|
|
1527
1637
|
return { visible: ready && resolved.fire && !alreadySeen, verdict: resolved };
|
|
1528
1638
|
}, [
|
|
1529
1639
|
featureEnabled,
|
|
1640
|
+
featuresSettled,
|
|
1530
1641
|
testing,
|
|
1531
1642
|
config,
|
|
1532
1643
|
decision,
|