@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.js
CHANGED
|
@@ -566,7 +566,6 @@ var feedbackInputHeights = (fonts, verticalPadding = 0) => {
|
|
|
566
566
|
|
|
567
567
|
// src/context/deviceId.ts
|
|
568
568
|
var AUTO_DEVICE_ID_PREFIX = "wdev_";
|
|
569
|
-
var deviceIdStorageKey = (appId) => `wireai:analytics:deviceKey:${appId != null ? appId : "default"}`;
|
|
570
569
|
var randomChunk = () => Math.floor(Math.random() * 4294967296).toString(36).padStart(6, "0");
|
|
571
570
|
var mintDeviceId = () => {
|
|
572
571
|
const time = Date.now().toString(36);
|
|
@@ -581,65 +580,13 @@ var autoDeviceKeyRegistry = () => {
|
|
|
581
580
|
deviceKeyGlobal[AUTO_DEVICE_KEY_SLOT] = created;
|
|
582
581
|
return created;
|
|
583
582
|
};
|
|
584
|
-
var startHydration = (registry, appId, storage, minted) => {
|
|
585
|
-
if (!registry.pending) registry.pending = /* @__PURE__ */ new Map();
|
|
586
|
-
const existing = registry.pending.get(appId);
|
|
587
|
-
if (existing) return existing;
|
|
588
|
-
const slot = deviceIdStorageKey(appId);
|
|
589
|
-
const degraded = () => {
|
|
590
|
-
var _a;
|
|
591
|
-
return { value: (_a = registry.keys.get(appId)) != null ? _a : minted, durable: false };
|
|
592
|
-
};
|
|
593
|
-
const adopted = (value) => ({ value, durable: true });
|
|
594
|
-
let run;
|
|
595
|
-
try {
|
|
596
|
-
run = Promise.resolve(storage.getItem(slot)).then((saved) => {
|
|
597
|
-
const persisted = typeof saved === "string" && saved.trim() ? saved.trim() : void 0;
|
|
598
|
-
if (persisted) {
|
|
599
|
-
registry.keys.set(appId, persisted);
|
|
600
|
-
return adopted(persisted);
|
|
601
|
-
}
|
|
602
|
-
return Promise.resolve(storage.setItem(slot, minted)).then(
|
|
603
|
-
() => {
|
|
604
|
-
var _a;
|
|
605
|
-
return adopted((_a = registry.keys.get(appId)) != null ? _a : minted);
|
|
606
|
-
},
|
|
607
|
-
degraded
|
|
608
|
-
);
|
|
609
|
-
}).catch(degraded);
|
|
610
|
-
} catch {
|
|
611
|
-
run = Promise.resolve(degraded());
|
|
612
|
-
}
|
|
613
|
-
registry.pending.set(appId, run);
|
|
614
|
-
void run.then((outcome) => {
|
|
615
|
-
var _a;
|
|
616
|
-
if (outcome.durable) return;
|
|
617
|
-
(_a = registry.pending) == null ? void 0 : _a.delete(appId);
|
|
618
|
-
registry.hydrating.delete(appId);
|
|
619
|
-
});
|
|
620
|
-
return run;
|
|
621
|
-
};
|
|
622
|
-
var resolveAutoDeviceKey = (opts = {}) => {
|
|
623
|
-
var _a, _b;
|
|
624
|
-
const registry = autoDeviceKeyRegistry();
|
|
625
|
-
const appId = (_a = opts.appId) != null ? _a : "default";
|
|
626
|
-
let id = registry.keys.get(appId);
|
|
627
|
-
if (!id) {
|
|
628
|
-
id = mintDeviceId();
|
|
629
|
-
registry.keys.set(appId, id);
|
|
630
|
-
}
|
|
631
|
-
const storage = opts.storage;
|
|
632
|
-
if (storage && !registry.hydrating.has(appId)) {
|
|
633
|
-
registry.hydrating.add(appId);
|
|
634
|
-
void startHydration(registry, appId, storage, id);
|
|
635
|
-
}
|
|
636
|
-
return (_b = registry.keys.get(appId)) != null ? _b : id;
|
|
637
|
-
};
|
|
638
583
|
var ambientAutoDeviceKey = () => {
|
|
639
584
|
const registry = autoDeviceKeyRegistry();
|
|
640
|
-
if (registry.keys.size === 0) return resolveAutoDeviceKey();
|
|
641
585
|
if (registry.keys.size > 1) return void 0;
|
|
642
|
-
|
|
586
|
+
const sole = [...registry.keys.values()][0];
|
|
587
|
+
if (sole) return sole;
|
|
588
|
+
if (!registry.ambient) registry.ambient = mintDeviceId();
|
|
589
|
+
return registry.ambient;
|
|
643
590
|
};
|
|
644
591
|
|
|
645
592
|
// src/reviews/idempotency.ts
|
|
@@ -831,6 +778,28 @@ var requestStoreReview = async (store) => {
|
|
|
831
778
|
return opened ? "store_url" : "none";
|
|
832
779
|
};
|
|
833
780
|
|
|
781
|
+
// src/utils/withDeadline.ts
|
|
782
|
+
var DEADLINE_EXPIRED = /* @__PURE__ */ Symbol("wire-deadline-expired");
|
|
783
|
+
var DEFAULT_DEADLINE_MS = 15e3;
|
|
784
|
+
var withDeadline = async (exchange, timeoutMs = DEFAULT_DEADLINE_MS) => {
|
|
785
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
786
|
+
let timer;
|
|
787
|
+
const expired = new Promise((resolve) => {
|
|
788
|
+
timer = setTimeout(() => {
|
|
789
|
+
controller == null ? void 0 : controller.abort();
|
|
790
|
+
resolve(DEADLINE_EXPIRED);
|
|
791
|
+
}, timeoutMs);
|
|
792
|
+
});
|
|
793
|
+
const running = exchange(controller == null ? void 0 : controller.signal);
|
|
794
|
+
try {
|
|
795
|
+
return await Promise.race([running, expired]);
|
|
796
|
+
} finally {
|
|
797
|
+
clearTimeout(timer);
|
|
798
|
+
void running.catch(() => {
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
|
|
834
803
|
// src/analytics/reportClientEvent.ts
|
|
835
804
|
var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
836
805
|
var toWireEvents = (events) => events.map((event) => {
|
|
@@ -872,17 +841,51 @@ var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
|
|
|
872
841
|
"@wireai/activation:currentSessionId"
|
|
873
842
|
);
|
|
874
843
|
var globalSlot = globalThis;
|
|
844
|
+
var SESSION_ID_LISTENERS_SLOT = /* @__PURE__ */ Symbol.for(
|
|
845
|
+
"@wireai/activation:currentSessionIdListeners"
|
|
846
|
+
);
|
|
847
|
+
var listenerSlot = globalThis;
|
|
848
|
+
var listeners = () => {
|
|
849
|
+
const existing = listenerSlot[SESSION_ID_LISTENERS_SLOT];
|
|
850
|
+
if (existing) return existing;
|
|
851
|
+
const created = /* @__PURE__ */ new Set();
|
|
852
|
+
listenerSlot[SESSION_ID_LISTENERS_SLOT] = created;
|
|
853
|
+
return created;
|
|
854
|
+
};
|
|
855
|
+
var writeCurrentSessionId = (id) => {
|
|
856
|
+
if (globalSlot[CURRENT_SESSION_ID_SLOT] === id) return;
|
|
857
|
+
globalSlot[CURRENT_SESSION_ID_SLOT] = id;
|
|
858
|
+
for (const listener of Array.from(listeners())) {
|
|
859
|
+
try {
|
|
860
|
+
listener();
|
|
861
|
+
} catch {
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
var subscribeCurrentSessionId = (listener) => {
|
|
866
|
+
const set = listeners();
|
|
867
|
+
set.add(listener);
|
|
868
|
+
return () => {
|
|
869
|
+
set.delete(listener);
|
|
870
|
+
};
|
|
871
|
+
};
|
|
875
872
|
var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
|
|
876
873
|
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.";
|
|
877
874
|
var MINT_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
|
|
878
875
|
"@wireai/activation:currentSessionIdMintWarned"
|
|
879
876
|
);
|
|
880
877
|
var warnSlot = globalThis;
|
|
878
|
+
var MINTED_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
|
|
879
|
+
"@wireai/activation:mintedSessionId"
|
|
880
|
+
);
|
|
881
|
+
var mintedSlot = globalThis;
|
|
882
|
+
var isMintedSessionId = (id) => id !== void 0 && id === mintedSlot[MINTED_SESSION_ID_SLOT];
|
|
881
883
|
var ensureCurrentSessionId = () => {
|
|
882
884
|
const existing = globalSlot[CURRENT_SESSION_ID_SLOT];
|
|
883
885
|
if (typeof existing === "string" && existing.length > 0) return existing;
|
|
884
886
|
const minted = makeSessionId();
|
|
885
|
-
|
|
887
|
+
writeCurrentSessionId(minted);
|
|
888
|
+
mintedSlot[MINTED_SESSION_ID_SLOT] = minted;
|
|
886
889
|
if (!warnSlot[MINT_WARNED_SLOT] && warnInDev(MINT_WARNING)) {
|
|
887
890
|
warnSlot[MINT_WARNED_SLOT] = true;
|
|
888
891
|
}
|
|
@@ -896,11 +899,15 @@ var submitReview = async (target, review) => {
|
|
|
896
899
|
const url = `${target.serverUrl.replace(/\/$/, "")}/v1/reviews`;
|
|
897
900
|
const headers = { "Content-Type": "application/json" };
|
|
898
901
|
if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
|
|
899
|
-
const res = await
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
902
|
+
const res = await withDeadline(
|
|
903
|
+
(signal) => fetch(url, {
|
|
904
|
+
method: "POST",
|
|
905
|
+
headers,
|
|
906
|
+
body: JSON.stringify(review),
|
|
907
|
+
signal
|
|
908
|
+
})
|
|
909
|
+
);
|
|
910
|
+
if (res === DEADLINE_EXPIRED) return "unsent";
|
|
904
911
|
if (!res) return "unsent";
|
|
905
912
|
return res.ok ? "accepted" : "rejected";
|
|
906
913
|
} catch {
|
|
@@ -918,9 +925,12 @@ var fetchReviewDecision = async (target, options = {}) => {
|
|
|
918
925
|
const url = `${base}/v1/reviews/decision${qs ? `?${qs}` : ""}`;
|
|
919
926
|
const headers = {};
|
|
920
927
|
if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
|
|
921
|
-
const
|
|
922
|
-
|
|
923
|
-
|
|
928
|
+
const json = await withDeadline(async (signal) => {
|
|
929
|
+
const res = await fetch(url, { headers, signal });
|
|
930
|
+
if (!res || !res.ok) return null;
|
|
931
|
+
return await res.json();
|
|
932
|
+
});
|
|
933
|
+
if (json === DEADLINE_EXPIRED) return null;
|
|
924
934
|
if (!json || typeof json.fire !== "boolean") return null;
|
|
925
935
|
return json;
|
|
926
936
|
} catch {
|
|
@@ -1015,6 +1025,10 @@ var _ReviewGate = ({
|
|
|
1015
1025
|
);
|
|
1016
1026
|
const postedRef = React7__default.default.useRef(false);
|
|
1017
1027
|
const unackedRef = React7__default.default.useRef(null);
|
|
1028
|
+
const recoverOnSettleRef = React7__default.default.useRef(false);
|
|
1029
|
+
const recoveredRef = React7__default.default.useRef(false);
|
|
1030
|
+
const postOnceRef = React7__default.default.useRef(() => {
|
|
1031
|
+
});
|
|
1018
1032
|
const postOnce = React7.useCallback(
|
|
1019
1033
|
(body) => {
|
|
1020
1034
|
if (postedRef.current) return;
|
|
@@ -1023,6 +1037,10 @@ var _ReviewGate = ({
|
|
|
1023
1037
|
void submitReview(target, body).then((result) => {
|
|
1024
1038
|
if (result === "unsent") {
|
|
1025
1039
|
postedRef.current = false;
|
|
1040
|
+
if (recoverOnSettleRef.current && !recoveredRef.current) {
|
|
1041
|
+
recoveredRef.current = true;
|
|
1042
|
+
postOnceRef.current(body);
|
|
1043
|
+
}
|
|
1026
1044
|
return;
|
|
1027
1045
|
}
|
|
1028
1046
|
unackedRef.current = null;
|
|
@@ -1030,6 +1048,7 @@ var _ReviewGate = ({
|
|
|
1030
1048
|
},
|
|
1031
1049
|
[target]
|
|
1032
1050
|
);
|
|
1051
|
+
postOnceRef.current = postOnce;
|
|
1033
1052
|
const abandonRef = React7__default.default.useRef(() => {
|
|
1034
1053
|
});
|
|
1035
1054
|
abandonRef.current = () => {
|
|
@@ -1038,6 +1057,10 @@ var _ReviewGate = ({
|
|
|
1038
1057
|
postOnce(undelivered);
|
|
1039
1058
|
return;
|
|
1040
1059
|
}
|
|
1060
|
+
if (postedRef.current && undelivered) {
|
|
1061
|
+
recoverOnSettleRef.current = true;
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1041
1064
|
if (postedRef.current || stars < 1) return;
|
|
1042
1065
|
postOnce(
|
|
1043
1066
|
buildBody({
|
|
@@ -1383,17 +1406,31 @@ var failOpen = async (storage, key) => {
|
|
|
1383
1406
|
const cached = await readCachedFeatures(storage, key);
|
|
1384
1407
|
return (_a = cached == null ? void 0 : cached.features) != null ? _a : defaultWireFeatures;
|
|
1385
1408
|
};
|
|
1386
|
-
var
|
|
1409
|
+
var NO_ANSWER = /* @__PURE__ */ Symbol("wire-features-no-answer");
|
|
1410
|
+
var fetchFeaturesJson = async (url, apiKey, timeoutMs) => {
|
|
1387
1411
|
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1412
|
+
let timer;
|
|
1413
|
+
const expired = new Promise((resolve) => {
|
|
1414
|
+
timer = setTimeout(() => {
|
|
1415
|
+
controller == null ? void 0 : controller.abort();
|
|
1416
|
+
resolve(NO_ANSWER);
|
|
1417
|
+
}, timeoutMs);
|
|
1418
|
+
});
|
|
1419
|
+
const exchange = (async () => {
|
|
1420
|
+
const res = await fetch(url, {
|
|
1391
1421
|
method: "GET",
|
|
1392
1422
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
1393
1423
|
signal: controller == null ? void 0 : controller.signal
|
|
1394
1424
|
});
|
|
1425
|
+
if (!res || !res.ok) return NO_ANSWER;
|
|
1426
|
+
return await res.json();
|
|
1427
|
+
})();
|
|
1428
|
+
try {
|
|
1429
|
+
return await Promise.race([exchange, expired]);
|
|
1395
1430
|
} finally {
|
|
1396
1431
|
clearTimeout(timer);
|
|
1432
|
+
void exchange.catch(() => {
|
|
1433
|
+
});
|
|
1397
1434
|
}
|
|
1398
1435
|
};
|
|
1399
1436
|
var fetchWireFeatures = async (config) => {
|
|
@@ -1403,9 +1440,8 @@ var fetchWireFeatures = async (config) => {
|
|
|
1403
1440
|
const url = `${config.serverUrl.replace(/\/$/, "")}/v1/features`;
|
|
1404
1441
|
const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
1405
1442
|
try {
|
|
1406
|
-
const
|
|
1407
|
-
if (
|
|
1408
|
-
const json = await res.json();
|
|
1443
|
+
const json = await fetchFeaturesJson(url, config.apiKey, timeoutMs);
|
|
1444
|
+
if (json === NO_ANSWER) return failOpen(storage, key);
|
|
1409
1445
|
const features = parseWireFeatures(json);
|
|
1410
1446
|
if (storage) writeCachedFeatures(storage, key, features);
|
|
1411
1447
|
return features;
|
|
@@ -1421,39 +1457,51 @@ var useStableValue = (value, isEqual) => {
|
|
|
1421
1457
|
return ref.current;
|
|
1422
1458
|
};
|
|
1423
1459
|
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);
|
|
1424
|
-
var
|
|
1460
|
+
var useWireFeaturesState = (config) => {
|
|
1425
1461
|
const stableConfig = useStableValue(config, sameFetchKey);
|
|
1426
1462
|
const [flags, setFlags] = React7.useState(defaultWireFeatures);
|
|
1427
1463
|
const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
|
|
1464
|
+
const [settled, setSettled] = React7.useState(!canFetch);
|
|
1428
1465
|
React7.useEffect(() => {
|
|
1429
1466
|
if (!canFetch) {
|
|
1430
1467
|
setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
|
|
1468
|
+
setSettled(true);
|
|
1431
1469
|
return;
|
|
1432
1470
|
}
|
|
1433
1471
|
let alive = true;
|
|
1434
1472
|
void fetchWireFeatures(stableConfig).then((next) => {
|
|
1435
1473
|
if (!alive) return;
|
|
1436
1474
|
setFlags((prev) => featuresEqual(prev, next) ? prev : next);
|
|
1475
|
+
setSettled(true);
|
|
1437
1476
|
});
|
|
1438
1477
|
return () => {
|
|
1439
1478
|
alive = false;
|
|
1440
1479
|
};
|
|
1441
1480
|
}, [canFetch, stableConfig]);
|
|
1442
|
-
return flags;
|
|
1481
|
+
return React7.useMemo(() => ({ flags, settled }), [flags, settled]);
|
|
1443
1482
|
};
|
|
1444
1483
|
var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
|
|
1445
|
-
var
|
|
1446
|
-
if (!
|
|
1447
|
-
|
|
1484
|
+
var featuresContextGlobal = globalThis;
|
|
1485
|
+
if (!featuresContextGlobal[CONTEXT_SYMBOL]) {
|
|
1486
|
+
featuresContextGlobal[CONTEXT_SYMBOL] = React7.createContext(null);
|
|
1487
|
+
}
|
|
1488
|
+
var WireFeaturesContext = featuresContextGlobal[CONTEXT_SYMBOL];
|
|
1489
|
+
var SETTLED_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.settled.context");
|
|
1490
|
+
var settledContextGlobal = globalThis;
|
|
1491
|
+
if (!settledContextGlobal[SETTLED_SYMBOL]) {
|
|
1492
|
+
settledContextGlobal[SETTLED_SYMBOL] = React7.createContext(true);
|
|
1448
1493
|
}
|
|
1449
|
-
var
|
|
1494
|
+
var WireFeaturesSettledContext = settledContextGlobal[SETTLED_SYMBOL];
|
|
1450
1495
|
var useWireFeaturesContext = () => React7.useContext(WireFeaturesContext);
|
|
1451
|
-
var
|
|
1496
|
+
var useResolvedFeaturesState = (options) => {
|
|
1452
1497
|
var _a, _b, _c;
|
|
1453
1498
|
const ctx = useWireFeaturesContext();
|
|
1499
|
+
const ctxSettled = React7.useContext(WireFeaturesSettledContext);
|
|
1454
1500
|
const shouldFetch = !(options == null ? void 0 : options.flags) && ctx == null;
|
|
1455
|
-
const fetched =
|
|
1456
|
-
|
|
1501
|
+
const fetched = useWireFeaturesState(shouldFetch ? options == null ? void 0 : options.config : void 0);
|
|
1502
|
+
const flags = (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched.flags) != null ? _c : defaultWireFeatures;
|
|
1503
|
+
const settled = (options == null ? void 0 : options.flags) ? true : ctx != null ? ctxSettled : fetched.settled;
|
|
1504
|
+
return React7.useMemo(() => ({ flags, settled }), [flags, settled]);
|
|
1457
1505
|
};
|
|
1458
1506
|
|
|
1459
1507
|
// src/reviews/equality.ts
|
|
@@ -1527,18 +1575,23 @@ var PROCESS_OPEN_ID_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:proces
|
|
|
1527
1575
|
var openIdGlobal = globalThis;
|
|
1528
1576
|
var currentOpenId = () => {
|
|
1529
1577
|
const live = getCurrentSessionId();
|
|
1578
|
+
const liveMinted = isMintedSessionId(live);
|
|
1530
1579
|
const pin = openIdGlobal[PROCESS_OPEN_ID_SLOT];
|
|
1531
1580
|
if (!pin) {
|
|
1532
1581
|
const openId = live != null ? live : makeSessionId();
|
|
1533
|
-
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId, observed: live };
|
|
1582
|
+
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId, observed: live, observedMinted: liveMinted };
|
|
1534
1583
|
return openId;
|
|
1535
1584
|
}
|
|
1536
1585
|
if (!live || live === pin.observed) return pin.openId;
|
|
1537
|
-
if (pin.observed === void 0) {
|
|
1538
|
-
openIdGlobal[PROCESS_OPEN_ID_SLOT] = {
|
|
1586
|
+
if (pin.observed === void 0 || pin.observedMinted) {
|
|
1587
|
+
openIdGlobal[PROCESS_OPEN_ID_SLOT] = {
|
|
1588
|
+
openId: pin.openId,
|
|
1589
|
+
observed: live,
|
|
1590
|
+
observedMinted: liveMinted
|
|
1591
|
+
};
|
|
1539
1592
|
return pin.openId;
|
|
1540
1593
|
}
|
|
1541
|
-
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId: live, observed: live };
|
|
1594
|
+
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId: live, observed: live, observedMinted: liveMinted };
|
|
1542
1595
|
return live;
|
|
1543
1596
|
};
|
|
1544
1597
|
var bumpSessionCount = (storage, sessionsKey, openKey, openId = currentOpenId()) => {
|
|
@@ -1576,17 +1629,32 @@ var useReviewGate = ({
|
|
|
1576
1629
|
decisionProp,
|
|
1577
1630
|
(a, b) => sameDecision(a, b)
|
|
1578
1631
|
);
|
|
1579
|
-
const
|
|
1632
|
+
const { flags: resolvedFeatures, settled: featuresSettled } = useResolvedFeaturesState({
|
|
1633
|
+
flags: features,
|
|
1634
|
+
config: featuresConfig
|
|
1635
|
+
});
|
|
1636
|
+
const featureEnabled = resolvedFeatures.review.enabled;
|
|
1580
1637
|
const testing = isTesting != null ? isTesting : isCoachmarkTesting();
|
|
1581
1638
|
const seenKey = reviewSeenKey(config.id, config.oncePerVersion === false ? void 0 : config.appVersion);
|
|
1582
1639
|
const lastKey = reviewLastShownKey(config.id);
|
|
1583
1640
|
const sessionsKey = reviewSessionsKey(config.id);
|
|
1584
1641
|
const sessionOpenKey = reviewSessionOpenKey(config.id);
|
|
1585
|
-
const
|
|
1642
|
+
const openSessionId = React7.useSyncExternalStore(
|
|
1643
|
+
subscribeCurrentSessionId,
|
|
1644
|
+
getCurrentSessionId,
|
|
1645
|
+
getCurrentSessionId
|
|
1646
|
+
);
|
|
1647
|
+
const storageRef = React7.useRef(storage);
|
|
1648
|
+
storageRef.current = storage;
|
|
1649
|
+
const [sessions, setSessions] = React7.useState(() => {
|
|
1586
1650
|
const store = resolveStorage(storage);
|
|
1587
1651
|
warnMissingGateStorage(store, "review");
|
|
1588
1652
|
return bumpSessionCount(store, sessionsKey, sessionOpenKey);
|
|
1589
|
-
})
|
|
1653
|
+
});
|
|
1654
|
+
React7.useEffect(() => {
|
|
1655
|
+
const next = bumpSessionCount(resolveStorage(storageRef.current), sessionsKey, sessionOpenKey);
|
|
1656
|
+
setSessions((prev) => prev === next ? prev : next);
|
|
1657
|
+
}, [openSessionId, sessionsKey, sessionOpenKey]);
|
|
1590
1658
|
const hasServerDecision = decision != null;
|
|
1591
1659
|
const timeoutMs = config.timeoutFallbackMs;
|
|
1592
1660
|
const waits = !hasServerDecision && timeoutMs != null && timeoutMs > 0;
|
|
@@ -1612,9 +1680,9 @@ var useReviewGate = ({
|
|
|
1612
1680
|
});
|
|
1613
1681
|
const resolved = decideReview(local, decision);
|
|
1614
1682
|
const alreadySeen = hasSeenGate(seenKey, storage, isTesting);
|
|
1615
|
-
const ready = hasServerDecision || elapsed;
|
|
1683
|
+
const ready = (hasServerDecision || elapsed) && featuresSettled;
|
|
1616
1684
|
return { visible: ready && resolved.fire && !alreadySeen, verdict: resolved };
|
|
1617
|
-
}, [featureEnabled, testing, config, decision, events, sessions, elapsed, hasServerDecision, seenKey, lastKey, storage, isTesting]);
|
|
1685
|
+
}, [featureEnabled, featuresSettled, testing, config, decision, events, sessions, elapsed, hasServerDecision, seenKey, lastKey, storage, isTesting]);
|
|
1618
1686
|
const shownRef = React7.useRef(false);
|
|
1619
1687
|
const markShown = React7.useCallback(() => {
|
|
1620
1688
|
if (shownRef.current) return;
|