@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
package/dist/reviews/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React7, { createContext, forwardRef, useRef, useState, useEffect, useCallback, useImperativeHandle, useMemo, useContext } from 'react';
|
|
1
|
+
import React7, { createContext, forwardRef, useRef, useState, useEffect, useCallback, useImperativeHandle, useMemo, useContext, useSyncExternalStore } from 'react';
|
|
2
2
|
import { StyleSheet, Easing, Animated, BackHandler, Modal, Pressable, Text, View, AccessibilityInfo, TextInput, Linking, Platform } from 'react-native';
|
|
3
3
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
@@ -560,7 +560,6 @@ var feedbackInputHeights = (fonts, verticalPadding = 0) => {
|
|
|
560
560
|
|
|
561
561
|
// src/context/deviceId.ts
|
|
562
562
|
var AUTO_DEVICE_ID_PREFIX = "wdev_";
|
|
563
|
-
var deviceIdStorageKey = (appId) => `wireai:analytics:deviceKey:${appId != null ? appId : "default"}`;
|
|
564
563
|
var randomChunk = () => Math.floor(Math.random() * 4294967296).toString(36).padStart(6, "0");
|
|
565
564
|
var mintDeviceId = () => {
|
|
566
565
|
const time = Date.now().toString(36);
|
|
@@ -575,65 +574,13 @@ var autoDeviceKeyRegistry = () => {
|
|
|
575
574
|
deviceKeyGlobal[AUTO_DEVICE_KEY_SLOT] = created;
|
|
576
575
|
return created;
|
|
577
576
|
};
|
|
578
|
-
var startHydration = (registry, appId, storage, minted) => {
|
|
579
|
-
if (!registry.pending) registry.pending = /* @__PURE__ */ new Map();
|
|
580
|
-
const existing = registry.pending.get(appId);
|
|
581
|
-
if (existing) return existing;
|
|
582
|
-
const slot = deviceIdStorageKey(appId);
|
|
583
|
-
const degraded = () => {
|
|
584
|
-
var _a;
|
|
585
|
-
return { value: (_a = registry.keys.get(appId)) != null ? _a : minted, durable: false };
|
|
586
|
-
};
|
|
587
|
-
const adopted = (value) => ({ value, durable: true });
|
|
588
|
-
let run;
|
|
589
|
-
try {
|
|
590
|
-
run = Promise.resolve(storage.getItem(slot)).then((saved) => {
|
|
591
|
-
const persisted = typeof saved === "string" && saved.trim() ? saved.trim() : void 0;
|
|
592
|
-
if (persisted) {
|
|
593
|
-
registry.keys.set(appId, persisted);
|
|
594
|
-
return adopted(persisted);
|
|
595
|
-
}
|
|
596
|
-
return Promise.resolve(storage.setItem(slot, minted)).then(
|
|
597
|
-
() => {
|
|
598
|
-
var _a;
|
|
599
|
-
return adopted((_a = registry.keys.get(appId)) != null ? _a : minted);
|
|
600
|
-
},
|
|
601
|
-
degraded
|
|
602
|
-
);
|
|
603
|
-
}).catch(degraded);
|
|
604
|
-
} catch {
|
|
605
|
-
run = Promise.resolve(degraded());
|
|
606
|
-
}
|
|
607
|
-
registry.pending.set(appId, run);
|
|
608
|
-
void run.then((outcome) => {
|
|
609
|
-
var _a;
|
|
610
|
-
if (outcome.durable) return;
|
|
611
|
-
(_a = registry.pending) == null ? void 0 : _a.delete(appId);
|
|
612
|
-
registry.hydrating.delete(appId);
|
|
613
|
-
});
|
|
614
|
-
return run;
|
|
615
|
-
};
|
|
616
|
-
var resolveAutoDeviceKey = (opts = {}) => {
|
|
617
|
-
var _a, _b;
|
|
618
|
-
const registry = autoDeviceKeyRegistry();
|
|
619
|
-
const appId = (_a = opts.appId) != null ? _a : "default";
|
|
620
|
-
let id = registry.keys.get(appId);
|
|
621
|
-
if (!id) {
|
|
622
|
-
id = mintDeviceId();
|
|
623
|
-
registry.keys.set(appId, id);
|
|
624
|
-
}
|
|
625
|
-
const storage = opts.storage;
|
|
626
|
-
if (storage && !registry.hydrating.has(appId)) {
|
|
627
|
-
registry.hydrating.add(appId);
|
|
628
|
-
void startHydration(registry, appId, storage, id);
|
|
629
|
-
}
|
|
630
|
-
return (_b = registry.keys.get(appId)) != null ? _b : id;
|
|
631
|
-
};
|
|
632
577
|
var ambientAutoDeviceKey = () => {
|
|
633
578
|
const registry = autoDeviceKeyRegistry();
|
|
634
|
-
if (registry.keys.size === 0) return resolveAutoDeviceKey();
|
|
635
579
|
if (registry.keys.size > 1) return void 0;
|
|
636
|
-
|
|
580
|
+
const sole = [...registry.keys.values()][0];
|
|
581
|
+
if (sole) return sole;
|
|
582
|
+
if (!registry.ambient) registry.ambient = mintDeviceId();
|
|
583
|
+
return registry.ambient;
|
|
637
584
|
};
|
|
638
585
|
|
|
639
586
|
// src/reviews/idempotency.ts
|
|
@@ -825,6 +772,28 @@ var requestStoreReview = async (store) => {
|
|
|
825
772
|
return opened ? "store_url" : "none";
|
|
826
773
|
};
|
|
827
774
|
|
|
775
|
+
// src/utils/withDeadline.ts
|
|
776
|
+
var DEADLINE_EXPIRED = /* @__PURE__ */ Symbol("wire-deadline-expired");
|
|
777
|
+
var DEFAULT_DEADLINE_MS = 15e3;
|
|
778
|
+
var withDeadline = async (exchange, timeoutMs = DEFAULT_DEADLINE_MS) => {
|
|
779
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
780
|
+
let timer;
|
|
781
|
+
const expired = new Promise((resolve) => {
|
|
782
|
+
timer = setTimeout(() => {
|
|
783
|
+
controller == null ? void 0 : controller.abort();
|
|
784
|
+
resolve(DEADLINE_EXPIRED);
|
|
785
|
+
}, timeoutMs);
|
|
786
|
+
});
|
|
787
|
+
const running = exchange(controller == null ? void 0 : controller.signal);
|
|
788
|
+
try {
|
|
789
|
+
return await Promise.race([running, expired]);
|
|
790
|
+
} finally {
|
|
791
|
+
clearTimeout(timer);
|
|
792
|
+
void running.catch(() => {
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
|
|
828
797
|
// src/analytics/reportClientEvent.ts
|
|
829
798
|
var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
830
799
|
var toWireEvents = (events) => events.map((event) => {
|
|
@@ -866,17 +835,51 @@ var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
|
|
|
866
835
|
"@wireai/activation:currentSessionId"
|
|
867
836
|
);
|
|
868
837
|
var globalSlot = globalThis;
|
|
838
|
+
var SESSION_ID_LISTENERS_SLOT = /* @__PURE__ */ Symbol.for(
|
|
839
|
+
"@wireai/activation:currentSessionIdListeners"
|
|
840
|
+
);
|
|
841
|
+
var listenerSlot = globalThis;
|
|
842
|
+
var listeners = () => {
|
|
843
|
+
const existing = listenerSlot[SESSION_ID_LISTENERS_SLOT];
|
|
844
|
+
if (existing) return existing;
|
|
845
|
+
const created = /* @__PURE__ */ new Set();
|
|
846
|
+
listenerSlot[SESSION_ID_LISTENERS_SLOT] = created;
|
|
847
|
+
return created;
|
|
848
|
+
};
|
|
849
|
+
var writeCurrentSessionId = (id) => {
|
|
850
|
+
if (globalSlot[CURRENT_SESSION_ID_SLOT] === id) return;
|
|
851
|
+
globalSlot[CURRENT_SESSION_ID_SLOT] = id;
|
|
852
|
+
for (const listener of Array.from(listeners())) {
|
|
853
|
+
try {
|
|
854
|
+
listener();
|
|
855
|
+
} catch {
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
var subscribeCurrentSessionId = (listener) => {
|
|
860
|
+
const set = listeners();
|
|
861
|
+
set.add(listener);
|
|
862
|
+
return () => {
|
|
863
|
+
set.delete(listener);
|
|
864
|
+
};
|
|
865
|
+
};
|
|
869
866
|
var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
|
|
870
867
|
var MINT_WARNING = "[wireai] No app-open session was registered, so a session id was minted for this event (the server drops an event that has no session_id, and still answers 200). Mount useLifecycleEvents at your app root so events correlate to a real app-open.";
|
|
871
868
|
var MINT_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
|
|
872
869
|
"@wireai/activation:currentSessionIdMintWarned"
|
|
873
870
|
);
|
|
874
871
|
var warnSlot = globalThis;
|
|
872
|
+
var MINTED_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
|
|
873
|
+
"@wireai/activation:mintedSessionId"
|
|
874
|
+
);
|
|
875
|
+
var mintedSlot = globalThis;
|
|
876
|
+
var isMintedSessionId = (id) => id !== void 0 && id === mintedSlot[MINTED_SESSION_ID_SLOT];
|
|
875
877
|
var ensureCurrentSessionId = () => {
|
|
876
878
|
const existing = globalSlot[CURRENT_SESSION_ID_SLOT];
|
|
877
879
|
if (typeof existing === "string" && existing.length > 0) return existing;
|
|
878
880
|
const minted = makeSessionId();
|
|
879
|
-
|
|
881
|
+
writeCurrentSessionId(minted);
|
|
882
|
+
mintedSlot[MINTED_SESSION_ID_SLOT] = minted;
|
|
880
883
|
if (!warnSlot[MINT_WARNED_SLOT] && warnInDev(MINT_WARNING)) {
|
|
881
884
|
warnSlot[MINT_WARNED_SLOT] = true;
|
|
882
885
|
}
|
|
@@ -890,11 +893,15 @@ var submitReview = async (target, review) => {
|
|
|
890
893
|
const url = `${target.serverUrl.replace(/\/$/, "")}/v1/reviews`;
|
|
891
894
|
const headers = { "Content-Type": "application/json" };
|
|
892
895
|
if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
|
|
893
|
-
const res = await
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
896
|
+
const res = await withDeadline(
|
|
897
|
+
(signal) => fetch(url, {
|
|
898
|
+
method: "POST",
|
|
899
|
+
headers,
|
|
900
|
+
body: JSON.stringify(review),
|
|
901
|
+
signal
|
|
902
|
+
})
|
|
903
|
+
);
|
|
904
|
+
if (res === DEADLINE_EXPIRED) return "unsent";
|
|
898
905
|
if (!res) return "unsent";
|
|
899
906
|
return res.ok ? "accepted" : "rejected";
|
|
900
907
|
} catch {
|
|
@@ -912,9 +919,12 @@ var fetchReviewDecision = async (target, options = {}) => {
|
|
|
912
919
|
const url = `${base}/v1/reviews/decision${qs ? `?${qs}` : ""}`;
|
|
913
920
|
const headers = {};
|
|
914
921
|
if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
|
|
922
|
+
const json = await withDeadline(async (signal) => {
|
|
923
|
+
const res = await fetch(url, { headers, signal });
|
|
924
|
+
if (!res || !res.ok) return null;
|
|
925
|
+
return await res.json();
|
|
926
|
+
});
|
|
927
|
+
if (json === DEADLINE_EXPIRED) return null;
|
|
918
928
|
if (!json || typeof json.fire !== "boolean") return null;
|
|
919
929
|
return json;
|
|
920
930
|
} catch {
|
|
@@ -1009,6 +1019,10 @@ var _ReviewGate = ({
|
|
|
1009
1019
|
);
|
|
1010
1020
|
const postedRef = React7.useRef(false);
|
|
1011
1021
|
const unackedRef = React7.useRef(null);
|
|
1022
|
+
const recoverOnSettleRef = React7.useRef(false);
|
|
1023
|
+
const recoveredRef = React7.useRef(false);
|
|
1024
|
+
const postOnceRef = React7.useRef(() => {
|
|
1025
|
+
});
|
|
1012
1026
|
const postOnce = useCallback(
|
|
1013
1027
|
(body) => {
|
|
1014
1028
|
if (postedRef.current) return;
|
|
@@ -1017,6 +1031,10 @@ var _ReviewGate = ({
|
|
|
1017
1031
|
void submitReview(target, body).then((result) => {
|
|
1018
1032
|
if (result === "unsent") {
|
|
1019
1033
|
postedRef.current = false;
|
|
1034
|
+
if (recoverOnSettleRef.current && !recoveredRef.current) {
|
|
1035
|
+
recoveredRef.current = true;
|
|
1036
|
+
postOnceRef.current(body);
|
|
1037
|
+
}
|
|
1020
1038
|
return;
|
|
1021
1039
|
}
|
|
1022
1040
|
unackedRef.current = null;
|
|
@@ -1024,6 +1042,7 @@ var _ReviewGate = ({
|
|
|
1024
1042
|
},
|
|
1025
1043
|
[target]
|
|
1026
1044
|
);
|
|
1045
|
+
postOnceRef.current = postOnce;
|
|
1027
1046
|
const abandonRef = React7.useRef(() => {
|
|
1028
1047
|
});
|
|
1029
1048
|
abandonRef.current = () => {
|
|
@@ -1032,6 +1051,10 @@ var _ReviewGate = ({
|
|
|
1032
1051
|
postOnce(undelivered);
|
|
1033
1052
|
return;
|
|
1034
1053
|
}
|
|
1054
|
+
if (postedRef.current && undelivered) {
|
|
1055
|
+
recoverOnSettleRef.current = true;
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1035
1058
|
if (postedRef.current || stars < 1) return;
|
|
1036
1059
|
postOnce(
|
|
1037
1060
|
buildBody({
|
|
@@ -1377,17 +1400,31 @@ var failOpen = async (storage, key) => {
|
|
|
1377
1400
|
const cached = await readCachedFeatures(storage, key);
|
|
1378
1401
|
return (_a = cached == null ? void 0 : cached.features) != null ? _a : defaultWireFeatures;
|
|
1379
1402
|
};
|
|
1380
|
-
var
|
|
1403
|
+
var NO_ANSWER = /* @__PURE__ */ Symbol("wire-features-no-answer");
|
|
1404
|
+
var fetchFeaturesJson = async (url, apiKey, timeoutMs) => {
|
|
1381
1405
|
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1406
|
+
let timer;
|
|
1407
|
+
const expired = new Promise((resolve) => {
|
|
1408
|
+
timer = setTimeout(() => {
|
|
1409
|
+
controller == null ? void 0 : controller.abort();
|
|
1410
|
+
resolve(NO_ANSWER);
|
|
1411
|
+
}, timeoutMs);
|
|
1412
|
+
});
|
|
1413
|
+
const exchange = (async () => {
|
|
1414
|
+
const res = await fetch(url, {
|
|
1385
1415
|
method: "GET",
|
|
1386
1416
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
1387
1417
|
signal: controller == null ? void 0 : controller.signal
|
|
1388
1418
|
});
|
|
1419
|
+
if (!res || !res.ok) return NO_ANSWER;
|
|
1420
|
+
return await res.json();
|
|
1421
|
+
})();
|
|
1422
|
+
try {
|
|
1423
|
+
return await Promise.race([exchange, expired]);
|
|
1389
1424
|
} finally {
|
|
1390
1425
|
clearTimeout(timer);
|
|
1426
|
+
void exchange.catch(() => {
|
|
1427
|
+
});
|
|
1391
1428
|
}
|
|
1392
1429
|
};
|
|
1393
1430
|
var fetchWireFeatures = async (config) => {
|
|
@@ -1397,9 +1434,8 @@ var fetchWireFeatures = async (config) => {
|
|
|
1397
1434
|
const url = `${config.serverUrl.replace(/\/$/, "")}/v1/features`;
|
|
1398
1435
|
const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
1399
1436
|
try {
|
|
1400
|
-
const
|
|
1401
|
-
if (
|
|
1402
|
-
const json = await res.json();
|
|
1437
|
+
const json = await fetchFeaturesJson(url, config.apiKey, timeoutMs);
|
|
1438
|
+
if (json === NO_ANSWER) return failOpen(storage, key);
|
|
1403
1439
|
const features = parseWireFeatures(json);
|
|
1404
1440
|
if (storage) writeCachedFeatures(storage, key, features);
|
|
1405
1441
|
return features;
|
|
@@ -1415,39 +1451,51 @@ var useStableValue = (value, isEqual) => {
|
|
|
1415
1451
|
return ref.current;
|
|
1416
1452
|
};
|
|
1417
1453
|
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);
|
|
1418
|
-
var
|
|
1454
|
+
var useWireFeaturesState = (config) => {
|
|
1419
1455
|
const stableConfig = useStableValue(config, sameFetchKey);
|
|
1420
1456
|
const [flags, setFlags] = useState(defaultWireFeatures);
|
|
1421
1457
|
const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
|
|
1458
|
+
const [settled, setSettled] = useState(!canFetch);
|
|
1422
1459
|
useEffect(() => {
|
|
1423
1460
|
if (!canFetch) {
|
|
1424
1461
|
setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
|
|
1462
|
+
setSettled(true);
|
|
1425
1463
|
return;
|
|
1426
1464
|
}
|
|
1427
1465
|
let alive = true;
|
|
1428
1466
|
void fetchWireFeatures(stableConfig).then((next) => {
|
|
1429
1467
|
if (!alive) return;
|
|
1430
1468
|
setFlags((prev) => featuresEqual(prev, next) ? prev : next);
|
|
1469
|
+
setSettled(true);
|
|
1431
1470
|
});
|
|
1432
1471
|
return () => {
|
|
1433
1472
|
alive = false;
|
|
1434
1473
|
};
|
|
1435
1474
|
}, [canFetch, stableConfig]);
|
|
1436
|
-
return flags;
|
|
1475
|
+
return useMemo(() => ({ flags, settled }), [flags, settled]);
|
|
1437
1476
|
};
|
|
1438
1477
|
var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
|
|
1439
|
-
var
|
|
1440
|
-
if (!
|
|
1441
|
-
|
|
1478
|
+
var featuresContextGlobal = globalThis;
|
|
1479
|
+
if (!featuresContextGlobal[CONTEXT_SYMBOL]) {
|
|
1480
|
+
featuresContextGlobal[CONTEXT_SYMBOL] = createContext(null);
|
|
1481
|
+
}
|
|
1482
|
+
var WireFeaturesContext = featuresContextGlobal[CONTEXT_SYMBOL];
|
|
1483
|
+
var SETTLED_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.settled.context");
|
|
1484
|
+
var settledContextGlobal = globalThis;
|
|
1485
|
+
if (!settledContextGlobal[SETTLED_SYMBOL]) {
|
|
1486
|
+
settledContextGlobal[SETTLED_SYMBOL] = createContext(true);
|
|
1442
1487
|
}
|
|
1443
|
-
var
|
|
1488
|
+
var WireFeaturesSettledContext = settledContextGlobal[SETTLED_SYMBOL];
|
|
1444
1489
|
var useWireFeaturesContext = () => useContext(WireFeaturesContext);
|
|
1445
|
-
var
|
|
1490
|
+
var useResolvedFeaturesState = (options) => {
|
|
1446
1491
|
var _a, _b, _c;
|
|
1447
1492
|
const ctx = useWireFeaturesContext();
|
|
1493
|
+
const ctxSettled = useContext(WireFeaturesSettledContext);
|
|
1448
1494
|
const shouldFetch = !(options == null ? void 0 : options.flags) && ctx == null;
|
|
1449
|
-
const fetched =
|
|
1450
|
-
|
|
1495
|
+
const fetched = useWireFeaturesState(shouldFetch ? options == null ? void 0 : options.config : void 0);
|
|
1496
|
+
const flags = (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched.flags) != null ? _c : defaultWireFeatures;
|
|
1497
|
+
const settled = (options == null ? void 0 : options.flags) ? true : ctx != null ? ctxSettled : fetched.settled;
|
|
1498
|
+
return useMemo(() => ({ flags, settled }), [flags, settled]);
|
|
1451
1499
|
};
|
|
1452
1500
|
|
|
1453
1501
|
// src/reviews/equality.ts
|
|
@@ -1521,18 +1569,23 @@ var PROCESS_OPEN_ID_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:proces
|
|
|
1521
1569
|
var openIdGlobal = globalThis;
|
|
1522
1570
|
var currentOpenId = () => {
|
|
1523
1571
|
const live = getCurrentSessionId();
|
|
1572
|
+
const liveMinted = isMintedSessionId(live);
|
|
1524
1573
|
const pin = openIdGlobal[PROCESS_OPEN_ID_SLOT];
|
|
1525
1574
|
if (!pin) {
|
|
1526
1575
|
const openId = live != null ? live : makeSessionId();
|
|
1527
|
-
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId, observed: live };
|
|
1576
|
+
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId, observed: live, observedMinted: liveMinted };
|
|
1528
1577
|
return openId;
|
|
1529
1578
|
}
|
|
1530
1579
|
if (!live || live === pin.observed) return pin.openId;
|
|
1531
|
-
if (pin.observed === void 0) {
|
|
1532
|
-
openIdGlobal[PROCESS_OPEN_ID_SLOT] = {
|
|
1580
|
+
if (pin.observed === void 0 || pin.observedMinted) {
|
|
1581
|
+
openIdGlobal[PROCESS_OPEN_ID_SLOT] = {
|
|
1582
|
+
openId: pin.openId,
|
|
1583
|
+
observed: live,
|
|
1584
|
+
observedMinted: liveMinted
|
|
1585
|
+
};
|
|
1533
1586
|
return pin.openId;
|
|
1534
1587
|
}
|
|
1535
|
-
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId: live, observed: live };
|
|
1588
|
+
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId: live, observed: live, observedMinted: liveMinted };
|
|
1536
1589
|
return live;
|
|
1537
1590
|
};
|
|
1538
1591
|
var bumpSessionCount = (storage, sessionsKey, openKey, openId = currentOpenId()) => {
|
|
@@ -1570,17 +1623,32 @@ var useReviewGate = ({
|
|
|
1570
1623
|
decisionProp,
|
|
1571
1624
|
(a, b) => sameDecision(a, b)
|
|
1572
1625
|
);
|
|
1573
|
-
const
|
|
1626
|
+
const { flags: resolvedFeatures, settled: featuresSettled } = useResolvedFeaturesState({
|
|
1627
|
+
flags: features,
|
|
1628
|
+
config: featuresConfig
|
|
1629
|
+
});
|
|
1630
|
+
const featureEnabled = resolvedFeatures.review.enabled;
|
|
1574
1631
|
const testing = isTesting != null ? isTesting : isCoachmarkTesting();
|
|
1575
1632
|
const seenKey = reviewSeenKey(config.id, config.oncePerVersion === false ? void 0 : config.appVersion);
|
|
1576
1633
|
const lastKey = reviewLastShownKey(config.id);
|
|
1577
1634
|
const sessionsKey = reviewSessionsKey(config.id);
|
|
1578
1635
|
const sessionOpenKey = reviewSessionOpenKey(config.id);
|
|
1579
|
-
const
|
|
1636
|
+
const openSessionId = useSyncExternalStore(
|
|
1637
|
+
subscribeCurrentSessionId,
|
|
1638
|
+
getCurrentSessionId,
|
|
1639
|
+
getCurrentSessionId
|
|
1640
|
+
);
|
|
1641
|
+
const storageRef = useRef(storage);
|
|
1642
|
+
storageRef.current = storage;
|
|
1643
|
+
const [sessions, setSessions] = useState(() => {
|
|
1580
1644
|
const store = resolveStorage(storage);
|
|
1581
1645
|
warnMissingGateStorage(store, "review");
|
|
1582
1646
|
return bumpSessionCount(store, sessionsKey, sessionOpenKey);
|
|
1583
|
-
})
|
|
1647
|
+
});
|
|
1648
|
+
useEffect(() => {
|
|
1649
|
+
const next = bumpSessionCount(resolveStorage(storageRef.current), sessionsKey, sessionOpenKey);
|
|
1650
|
+
setSessions((prev) => prev === next ? prev : next);
|
|
1651
|
+
}, [openSessionId, sessionsKey, sessionOpenKey]);
|
|
1584
1652
|
const hasServerDecision = decision != null;
|
|
1585
1653
|
const timeoutMs = config.timeoutFallbackMs;
|
|
1586
1654
|
const waits = !hasServerDecision && timeoutMs != null && timeoutMs > 0;
|
|
@@ -1606,9 +1674,9 @@ var useReviewGate = ({
|
|
|
1606
1674
|
});
|
|
1607
1675
|
const resolved = decideReview(local, decision);
|
|
1608
1676
|
const alreadySeen = hasSeenGate(seenKey, storage, isTesting);
|
|
1609
|
-
const ready = hasServerDecision || elapsed;
|
|
1677
|
+
const ready = (hasServerDecision || elapsed) && featuresSettled;
|
|
1610
1678
|
return { visible: ready && resolved.fire && !alreadySeen, verdict: resolved };
|
|
1611
|
-
}, [featureEnabled, testing, config, decision, events, sessions, elapsed, hasServerDecision, seenKey, lastKey, storage, isTesting]);
|
|
1679
|
+
}, [featureEnabled, featuresSettled, testing, config, decision, events, sessions, elapsed, hasServerDecision, seenKey, lastKey, storage, isTesting]);
|
|
1612
1680
|
const shownRef = useRef(false);
|
|
1613
1681
|
const markShown = useCallback(() => {
|
|
1614
1682
|
if (shownRef.current) return;
|