@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,9 +1,9 @@
|
|
|
1
1
|
import React__default from 'react';
|
|
2
2
|
import { O as OnboardingTheme } from '../types-BKfpdZzX.mjs';
|
|
3
|
-
import { C as CoachmarkStorage } from '../types-
|
|
3
|
+
import { C as CoachmarkStorage } from '../types-D_0B0yay.mjs';
|
|
4
4
|
import { a as WireFeatures, b as WireFeaturesConfig } from '../types-BpwiRpA8.mjs';
|
|
5
|
-
import { S as SubmitResult } from '../types-
|
|
6
|
-
export { G as GateRules, a as GateSignals, e as evaluateGate, r as resolveRules } from '../decision-
|
|
5
|
+
import { S as SubmitResult } from '../types-Byx306Kv.mjs';
|
|
6
|
+
export { G as GateRules, a as GateSignals, e as evaluateGate, r as resolveRules } from '../decision-yBj2AyPW.mjs';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* The firing verdict - the SAME contract on both sides of the seam. `decideQuestionnaire`
|
|
@@ -246,19 +246,6 @@ declare const buildQuestionnaireSubmission: (input: {
|
|
|
246
246
|
meta?: QuestionnaireMeta;
|
|
247
247
|
}) => QuestionnaireSubmission;
|
|
248
248
|
|
|
249
|
-
/**
|
|
250
|
-
* transport.ts - kit → Wire server requests for the questionnaire module, never throwing
|
|
251
|
-
* (a pre-onboarding questionnaire must NEVER break the app). Mirrors `submitReview`: a thin
|
|
252
|
-
* fetch wrapper, Bearer tenant key, and — since 0.13.3 — the SAME three-outcome delivery ack.
|
|
253
|
-
*
|
|
254
|
-
* • submitQuestionnaireResponse → POST {serverUrl}/v1/questionnaires/{id}/responses
|
|
255
|
-
* • fetchQuestionnaireDecision → GET {serverUrl}/v1/questionnaires/decision (best-effort)
|
|
256
|
-
*
|
|
257
|
-
* The decision fetch is HARD fail-open on the "never show" side: an unreachable endpoint, a
|
|
258
|
-
* non-2xx (incl. 404), a missing `fetch`, or bad JSON all resolve to null, so a host wiring
|
|
259
|
-
* pure server-directed firing simply shows nothing - exactly the reviews decision seam.
|
|
260
|
-
*/
|
|
261
|
-
|
|
262
249
|
/**
|
|
263
250
|
* What became of a questionnaire POST. The SHARED verdict — identical values, identical meaning,
|
|
264
251
|
* identical retry rule as `ReviewSubmitResult`. The reasoning lives once, in `utils/submitResult.ts`.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React__default from 'react';
|
|
2
2
|
import { O as OnboardingTheme } from '../types-BKfpdZzX.js';
|
|
3
|
-
import { C as CoachmarkStorage } from '../types-
|
|
3
|
+
import { C as CoachmarkStorage } from '../types-D_0B0yay.js';
|
|
4
4
|
import { a as WireFeatures, b as WireFeaturesConfig } from '../types-BpwiRpA8.js';
|
|
5
|
-
import { S as SubmitResult } from '../types-
|
|
6
|
-
export { G as GateRules, a as GateSignals, e as evaluateGate, r as resolveRules } from '../decision-
|
|
5
|
+
import { S as SubmitResult } from '../types-tdATL5z0.js';
|
|
6
|
+
export { G as GateRules, a as GateSignals, e as evaluateGate, r as resolveRules } from '../decision-3vWLuBlO.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* The firing verdict - the SAME contract on both sides of the seam. `decideQuestionnaire`
|
|
@@ -246,19 +246,6 @@ declare const buildQuestionnaireSubmission: (input: {
|
|
|
246
246
|
meta?: QuestionnaireMeta;
|
|
247
247
|
}) => QuestionnaireSubmission;
|
|
248
248
|
|
|
249
|
-
/**
|
|
250
|
-
* transport.ts - kit → Wire server requests for the questionnaire module, never throwing
|
|
251
|
-
* (a pre-onboarding questionnaire must NEVER break the app). Mirrors `submitReview`: a thin
|
|
252
|
-
* fetch wrapper, Bearer tenant key, and — since 0.13.3 — the SAME three-outcome delivery ack.
|
|
253
|
-
*
|
|
254
|
-
* • submitQuestionnaireResponse → POST {serverUrl}/v1/questionnaires/{id}/responses
|
|
255
|
-
* • fetchQuestionnaireDecision → GET {serverUrl}/v1/questionnaires/decision (best-effort)
|
|
256
|
-
*
|
|
257
|
-
* The decision fetch is HARD fail-open on the "never show" side: an unreachable endpoint, a
|
|
258
|
-
* non-2xx (incl. 404), a missing `fetch`, or bad JSON all resolve to null, so a host wiring
|
|
259
|
-
* pure server-directed firing simply shows nothing - exactly the reviews decision seam.
|
|
260
|
-
*/
|
|
261
|
-
|
|
262
249
|
/**
|
|
263
250
|
* What became of a questionnaire POST. The SHARED verdict — identical values, identical meaning,
|
|
264
251
|
* identical retry rule as `ReviewSubmitResult`. The reasoning lives once, in `utils/submitResult.ts`.
|
|
@@ -843,6 +843,28 @@ var toAnswers = (drafts) => {
|
|
|
843
843
|
};
|
|
844
844
|
};
|
|
845
845
|
|
|
846
|
+
// src/utils/withDeadline.ts
|
|
847
|
+
var DEADLINE_EXPIRED = /* @__PURE__ */ Symbol("wire-deadline-expired");
|
|
848
|
+
var DEFAULT_DEADLINE_MS = 15e3;
|
|
849
|
+
var withDeadline = async (exchange, timeoutMs = DEFAULT_DEADLINE_MS) => {
|
|
850
|
+
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
851
|
+
let timer;
|
|
852
|
+
const expired = new Promise((resolve) => {
|
|
853
|
+
timer = setTimeout(() => {
|
|
854
|
+
controller == null ? void 0 : controller.abort();
|
|
855
|
+
resolve(DEADLINE_EXPIRED);
|
|
856
|
+
}, timeoutMs);
|
|
857
|
+
});
|
|
858
|
+
const running = exchange(controller == null ? void 0 : controller.signal);
|
|
859
|
+
try {
|
|
860
|
+
return await Promise.race([running, expired]);
|
|
861
|
+
} finally {
|
|
862
|
+
clearTimeout(timer);
|
|
863
|
+
void running.catch(() => {
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
|
|
846
868
|
// src/questionnaire/transport.ts
|
|
847
869
|
var submitQuestionnaireResponse = async (target, id, submission) => {
|
|
848
870
|
if (!(target == null ? void 0 : target.serverUrl) || !id) return "unsent";
|
|
@@ -851,11 +873,15 @@ var submitQuestionnaireResponse = async (target, id, submission) => {
|
|
|
851
873
|
const url = `${base}/v1/questionnaires/${encodeURIComponent(id)}/responses`;
|
|
852
874
|
const headers = { "Content-Type": "application/json" };
|
|
853
875
|
if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
|
|
854
|
-
const res = await
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
876
|
+
const res = await withDeadline(
|
|
877
|
+
(signal) => fetch(url, {
|
|
878
|
+
method: "POST",
|
|
879
|
+
headers,
|
|
880
|
+
body: JSON.stringify(submission),
|
|
881
|
+
signal
|
|
882
|
+
})
|
|
883
|
+
);
|
|
884
|
+
if (res === DEADLINE_EXPIRED) return "unsent";
|
|
859
885
|
if (!res) return "unsent";
|
|
860
886
|
return res.ok ? "accepted" : "rejected";
|
|
861
887
|
} catch {
|
|
@@ -873,9 +899,12 @@ var fetchQuestionnaireDecision = async (target, options = {}) => {
|
|
|
873
899
|
const url = `${base}/v1/questionnaires/decision${qs ? `?${qs}` : ""}`;
|
|
874
900
|
const headers = {};
|
|
875
901
|
if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
|
|
876
|
-
const
|
|
877
|
-
|
|
878
|
-
|
|
902
|
+
const json = await withDeadline(async (signal) => {
|
|
903
|
+
const res = await fetch(url, { headers, signal });
|
|
904
|
+
if (!res || !res.ok) return null;
|
|
905
|
+
return await res.json();
|
|
906
|
+
});
|
|
907
|
+
if (json === DEADLINE_EXPIRED) return null;
|
|
879
908
|
if (!json || typeof json.fire !== "boolean") return null;
|
|
880
909
|
return json;
|
|
881
910
|
} catch {
|
|
@@ -954,6 +983,10 @@ var _QuestionnaireGate = ({
|
|
|
954
983
|
}, [id, onResolved]);
|
|
955
984
|
const postedRef = React8.useRef(false);
|
|
956
985
|
const unackedRef = React8.useRef(null);
|
|
986
|
+
const recoverOnSettleRef = React8.useRef(false);
|
|
987
|
+
const recoveredRef = React8.useRef(false);
|
|
988
|
+
const postOnceRef = React8.useRef(() => {
|
|
989
|
+
});
|
|
957
990
|
const postOnce = React8.useCallback(
|
|
958
991
|
(body) => {
|
|
959
992
|
if (postedRef.current) return;
|
|
@@ -962,6 +995,10 @@ var _QuestionnaireGate = ({
|
|
|
962
995
|
void submitQuestionnaireResponse(target, id, body).then((result) => {
|
|
963
996
|
if (result === "unsent") {
|
|
964
997
|
postedRef.current = false;
|
|
998
|
+
if (recoverOnSettleRef.current && !recoveredRef.current) {
|
|
999
|
+
recoveredRef.current = true;
|
|
1000
|
+
postOnceRef.current(body);
|
|
1001
|
+
}
|
|
965
1002
|
return;
|
|
966
1003
|
}
|
|
967
1004
|
unackedRef.current = null;
|
|
@@ -969,11 +1006,16 @@ var _QuestionnaireGate = ({
|
|
|
969
1006
|
},
|
|
970
1007
|
[target, id]
|
|
971
1008
|
);
|
|
1009
|
+
postOnceRef.current = postOnce;
|
|
972
1010
|
const recoverRef = React8.useRef(() => {
|
|
973
1011
|
});
|
|
974
1012
|
recoverRef.current = () => {
|
|
975
1013
|
const undelivered = unackedRef.current;
|
|
976
|
-
if (undelivered && !postedRef.current)
|
|
1014
|
+
if (undelivered && !postedRef.current) {
|
|
1015
|
+
postOnce(undelivered);
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
if (postedRef.current && undelivered) recoverOnSettleRef.current = true;
|
|
977
1019
|
};
|
|
978
1020
|
React8__default.default.useEffect(() => () => recoverRef.current(), []);
|
|
979
1021
|
const finish = React8.useCallback(
|
|
@@ -1122,6 +1164,39 @@ var styles7 = reactNative.StyleSheet.create({
|
|
|
1122
1164
|
backGlyph: { includeFontPadding: false }
|
|
1123
1165
|
});
|
|
1124
1166
|
|
|
1167
|
+
// src/analytics/reportClientEvent.ts
|
|
1168
|
+
var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
1169
|
+
|
|
1170
|
+
// src/analytics/currentSession.ts
|
|
1171
|
+
var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
|
|
1172
|
+
"@wireai/activation:currentSessionId"
|
|
1173
|
+
);
|
|
1174
|
+
var globalSlot = globalThis;
|
|
1175
|
+
var SESSION_ID_LISTENERS_SLOT = /* @__PURE__ */ Symbol.for(
|
|
1176
|
+
"@wireai/activation:currentSessionIdListeners"
|
|
1177
|
+
);
|
|
1178
|
+
var listenerSlot = globalThis;
|
|
1179
|
+
var listeners = () => {
|
|
1180
|
+
const existing = listenerSlot[SESSION_ID_LISTENERS_SLOT];
|
|
1181
|
+
if (existing) return existing;
|
|
1182
|
+
const created = /* @__PURE__ */ new Set();
|
|
1183
|
+
listenerSlot[SESSION_ID_LISTENERS_SLOT] = created;
|
|
1184
|
+
return created;
|
|
1185
|
+
};
|
|
1186
|
+
var subscribeCurrentSessionId = (listener) => {
|
|
1187
|
+
const set = listeners();
|
|
1188
|
+
set.add(listener);
|
|
1189
|
+
return () => {
|
|
1190
|
+
set.delete(listener);
|
|
1191
|
+
};
|
|
1192
|
+
};
|
|
1193
|
+
var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
|
|
1194
|
+
var MINTED_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
|
|
1195
|
+
"@wireai/activation:mintedSessionId"
|
|
1196
|
+
);
|
|
1197
|
+
var mintedSlot = globalThis;
|
|
1198
|
+
var isMintedSessionId = (id) => id !== void 0 && id === mintedSlot[MINTED_SESSION_ID_SLOT];
|
|
1199
|
+
|
|
1125
1200
|
// src/coachmarks/runtime.ts
|
|
1126
1201
|
var COACHMARK_RUNTIME_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:coachmarkRuntime");
|
|
1127
1202
|
var runtimeGlobal = globalThis;
|
|
@@ -1135,9 +1210,54 @@ var coachmarkRuntime = () => {
|
|
|
1135
1210
|
var getCoachmarkStorage = () => coachmarkRuntime().storage;
|
|
1136
1211
|
var isCoachmarkTesting = () => coachmarkRuntime().testing;
|
|
1137
1212
|
var SEEN_VALUE = "1";
|
|
1213
|
+
var GATE_PROBE_KEY = "wire_gate_probe";
|
|
1214
|
+
var ASYNC_GATE_STORAGE_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
|
|
1215
|
+
"@wireai/activation:asyncGateStorageWarned"
|
|
1216
|
+
);
|
|
1217
|
+
var asyncGateWarnGlobal = globalThis;
|
|
1218
|
+
var closedGateStorage = Object.freeze({
|
|
1219
|
+
getItem: () => SEEN_VALUE,
|
|
1220
|
+
setItem: () => {
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
var syncGateAdapters = /* @__PURE__ */ new WeakSet();
|
|
1224
|
+
var asyncGateAdapters = /* @__PURE__ */ new WeakSet();
|
|
1225
|
+
var warnAsyncGateStorage = () => {
|
|
1226
|
+
if (asyncGateWarnGlobal[ASYNC_GATE_STORAGE_WARNED_SLOT]) return;
|
|
1227
|
+
if (typeof __DEV__ === "undefined" || !__DEV__) return;
|
|
1228
|
+
if (typeof console === "undefined" || !console.warn) return;
|
|
1229
|
+
asyncGateWarnGlobal[ASYNC_GATE_STORAGE_WARNED_SLOT] = true;
|
|
1230
|
+
console.warn(
|
|
1231
|
+
"[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."
|
|
1232
|
+
);
|
|
1233
|
+
};
|
|
1234
|
+
var validateGateStorage = (storage) => {
|
|
1235
|
+
if (!storage) return null;
|
|
1236
|
+
if (syncGateAdapters.has(storage)) return storage;
|
|
1237
|
+
if (asyncGateAdapters.has(storage)) return closedGateStorage;
|
|
1238
|
+
let probed;
|
|
1239
|
+
try {
|
|
1240
|
+
probed = storage.getItem(GATE_PROBE_KEY);
|
|
1241
|
+
} catch {
|
|
1242
|
+
syncGateAdapters.add(storage);
|
|
1243
|
+
return storage;
|
|
1244
|
+
}
|
|
1245
|
+
if (probed != null && typeof probed.then === "function") {
|
|
1246
|
+
asyncGateAdapters.add(storage);
|
|
1247
|
+
try {
|
|
1248
|
+
probed.then(void 0, () => {
|
|
1249
|
+
});
|
|
1250
|
+
} catch {
|
|
1251
|
+
}
|
|
1252
|
+
warnAsyncGateStorage();
|
|
1253
|
+
return closedGateStorage;
|
|
1254
|
+
}
|
|
1255
|
+
syncGateAdapters.add(storage);
|
|
1256
|
+
return storage;
|
|
1257
|
+
};
|
|
1138
1258
|
var hasSeenGate = (key, storageOverride, testingOverride) => {
|
|
1139
1259
|
if (testingOverride != null ? testingOverride : isCoachmarkTesting()) return false;
|
|
1140
|
-
const storage = storageOverride != null ? storageOverride : getCoachmarkStorage();
|
|
1260
|
+
const storage = validateGateStorage(storageOverride != null ? storageOverride : getCoachmarkStorage());
|
|
1141
1261
|
if (!storage) return false;
|
|
1142
1262
|
try {
|
|
1143
1263
|
return storage.getItem(key) === SEEN_VALUE;
|
|
@@ -1147,7 +1267,7 @@ var hasSeenGate = (key, storageOverride, testingOverride) => {
|
|
|
1147
1267
|
};
|
|
1148
1268
|
var markSeenGate = (key, storageOverride, testingOverride) => {
|
|
1149
1269
|
if (testingOverride != null ? testingOverride : isCoachmarkTesting()) return;
|
|
1150
|
-
const storage = storageOverride != null ? storageOverride : getCoachmarkStorage();
|
|
1270
|
+
const storage = validateGateStorage(storageOverride != null ? storageOverride : getCoachmarkStorage());
|
|
1151
1271
|
if (!storage) return;
|
|
1152
1272
|
try {
|
|
1153
1273
|
storage.setItem(key, SEEN_VALUE);
|
|
@@ -1235,17 +1355,31 @@ var failOpen = async (storage, key) => {
|
|
|
1235
1355
|
const cached = await readCachedFeatures(storage, key);
|
|
1236
1356
|
return (_a = cached == null ? void 0 : cached.features) != null ? _a : defaultWireFeatures;
|
|
1237
1357
|
};
|
|
1238
|
-
var
|
|
1358
|
+
var NO_ANSWER = /* @__PURE__ */ Symbol("wire-features-no-answer");
|
|
1359
|
+
var fetchFeaturesJson = async (url, apiKey, timeoutMs) => {
|
|
1239
1360
|
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1361
|
+
let timer;
|
|
1362
|
+
const expired = new Promise((resolve) => {
|
|
1363
|
+
timer = setTimeout(() => {
|
|
1364
|
+
controller == null ? void 0 : controller.abort();
|
|
1365
|
+
resolve(NO_ANSWER);
|
|
1366
|
+
}, timeoutMs);
|
|
1367
|
+
});
|
|
1368
|
+
const exchange = (async () => {
|
|
1369
|
+
const res = await fetch(url, {
|
|
1243
1370
|
method: "GET",
|
|
1244
1371
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
1245
1372
|
signal: controller == null ? void 0 : controller.signal
|
|
1246
1373
|
});
|
|
1374
|
+
if (!res || !res.ok) return NO_ANSWER;
|
|
1375
|
+
return await res.json();
|
|
1376
|
+
})();
|
|
1377
|
+
try {
|
|
1378
|
+
return await Promise.race([exchange, expired]);
|
|
1247
1379
|
} finally {
|
|
1248
1380
|
clearTimeout(timer);
|
|
1381
|
+
void exchange.catch(() => {
|
|
1382
|
+
});
|
|
1249
1383
|
}
|
|
1250
1384
|
};
|
|
1251
1385
|
var fetchWireFeatures = async (config) => {
|
|
@@ -1255,9 +1389,8 @@ var fetchWireFeatures = async (config) => {
|
|
|
1255
1389
|
const url = `${config.serverUrl.replace(/\/$/, "")}/v1/features`;
|
|
1256
1390
|
const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
1257
1391
|
try {
|
|
1258
|
-
const
|
|
1259
|
-
if (
|
|
1260
|
-
const json = await res.json();
|
|
1392
|
+
const json = await fetchFeaturesJson(url, config.apiKey, timeoutMs);
|
|
1393
|
+
if (json === NO_ANSWER) return failOpen(storage, key);
|
|
1261
1394
|
const features = parseWireFeatures(json);
|
|
1262
1395
|
if (storage) writeCachedFeatures(storage, key, features);
|
|
1263
1396
|
return features;
|
|
@@ -1273,39 +1406,51 @@ var useStableValue = (value, isEqual) => {
|
|
|
1273
1406
|
return ref.current;
|
|
1274
1407
|
};
|
|
1275
1408
|
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);
|
|
1276
|
-
var
|
|
1409
|
+
var useWireFeaturesState = (config) => {
|
|
1277
1410
|
const stableConfig = useStableValue(config, sameFetchKey);
|
|
1278
1411
|
const [flags, setFlags] = React8.useState(defaultWireFeatures);
|
|
1279
1412
|
const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
|
|
1413
|
+
const [settled, setSettled] = React8.useState(!canFetch);
|
|
1280
1414
|
React8.useEffect(() => {
|
|
1281
1415
|
if (!canFetch) {
|
|
1282
1416
|
setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
|
|
1417
|
+
setSettled(true);
|
|
1283
1418
|
return;
|
|
1284
1419
|
}
|
|
1285
1420
|
let alive = true;
|
|
1286
1421
|
void fetchWireFeatures(stableConfig).then((next) => {
|
|
1287
1422
|
if (!alive) return;
|
|
1288
1423
|
setFlags((prev) => featuresEqual(prev, next) ? prev : next);
|
|
1424
|
+
setSettled(true);
|
|
1289
1425
|
});
|
|
1290
1426
|
return () => {
|
|
1291
1427
|
alive = false;
|
|
1292
1428
|
};
|
|
1293
1429
|
}, [canFetch, stableConfig]);
|
|
1294
|
-
return flags;
|
|
1430
|
+
return React8.useMemo(() => ({ flags, settled }), [flags, settled]);
|
|
1295
1431
|
};
|
|
1296
1432
|
var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
|
|
1297
|
-
var
|
|
1298
|
-
if (!
|
|
1299
|
-
|
|
1433
|
+
var featuresContextGlobal = globalThis;
|
|
1434
|
+
if (!featuresContextGlobal[CONTEXT_SYMBOL]) {
|
|
1435
|
+
featuresContextGlobal[CONTEXT_SYMBOL] = React8.createContext(null);
|
|
1436
|
+
}
|
|
1437
|
+
var WireFeaturesContext = featuresContextGlobal[CONTEXT_SYMBOL];
|
|
1438
|
+
var SETTLED_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.settled.context");
|
|
1439
|
+
var settledContextGlobal = globalThis;
|
|
1440
|
+
if (!settledContextGlobal[SETTLED_SYMBOL]) {
|
|
1441
|
+
settledContextGlobal[SETTLED_SYMBOL] = React8.createContext(true);
|
|
1300
1442
|
}
|
|
1301
|
-
var
|
|
1443
|
+
var WireFeaturesSettledContext = settledContextGlobal[SETTLED_SYMBOL];
|
|
1302
1444
|
var useWireFeaturesContext = () => React8.useContext(WireFeaturesContext);
|
|
1303
|
-
var
|
|
1445
|
+
var useResolvedFeaturesState = (options) => {
|
|
1304
1446
|
var _a, _b, _c;
|
|
1305
1447
|
const ctx = useWireFeaturesContext();
|
|
1448
|
+
const ctxSettled = React8.useContext(WireFeaturesSettledContext);
|
|
1306
1449
|
const shouldFetch = !(options == null ? void 0 : options.flags) && ctx == null;
|
|
1307
|
-
const fetched =
|
|
1308
|
-
|
|
1450
|
+
const fetched = useWireFeaturesState(shouldFetch ? options == null ? void 0 : options.config : void 0);
|
|
1451
|
+
const flags = (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched.flags) != null ? _c : defaultWireFeatures;
|
|
1452
|
+
const settled = (options == null ? void 0 : options.flags) ? true : ctx != null ? ctxSettled : fetched.settled;
|
|
1453
|
+
return React8.useMemo(() => ({ flags, settled }), [flags, settled]);
|
|
1309
1454
|
};
|
|
1310
1455
|
|
|
1311
1456
|
// src/reviews/equality.ts
|
|
@@ -1329,18 +1474,8 @@ var sameDecision = (a, b) => {
|
|
|
1329
1474
|
return a.fire === b.fire && a.reason === b.reason;
|
|
1330
1475
|
};
|
|
1331
1476
|
|
|
1332
|
-
// src/analytics/reportClientEvent.ts
|
|
1333
|
-
var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
1334
|
-
|
|
1335
|
-
// src/analytics/currentSession.ts
|
|
1336
|
-
var CURRENT_SESSION_ID_SLOT = /* @__PURE__ */ Symbol.for(
|
|
1337
|
-
"@wireai/activation:currentSessionId"
|
|
1338
|
-
);
|
|
1339
|
-
var globalSlot = globalThis;
|
|
1340
|
-
var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
|
|
1341
|
-
|
|
1342
1477
|
// src/reviews/runtime.ts
|
|
1343
|
-
var resolveStorage = (override) => override != null ? override : getCoachmarkStorage();
|
|
1478
|
+
var resolveStorage = (override) => validateGateStorage(override != null ? override : getCoachmarkStorage());
|
|
1344
1479
|
var NO_STORAGE_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
|
|
1345
1480
|
"@wireai/activation:gateStorageWarned"
|
|
1346
1481
|
);
|
|
@@ -1385,18 +1520,23 @@ var PROCESS_OPEN_ID_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:proces
|
|
|
1385
1520
|
var openIdGlobal = globalThis;
|
|
1386
1521
|
var currentOpenId = () => {
|
|
1387
1522
|
const live = getCurrentSessionId();
|
|
1523
|
+
const liveMinted = isMintedSessionId(live);
|
|
1388
1524
|
const pin = openIdGlobal[PROCESS_OPEN_ID_SLOT];
|
|
1389
1525
|
if (!pin) {
|
|
1390
1526
|
const openId = live != null ? live : makeSessionId();
|
|
1391
|
-
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId, observed: live };
|
|
1527
|
+
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId, observed: live, observedMinted: liveMinted };
|
|
1392
1528
|
return openId;
|
|
1393
1529
|
}
|
|
1394
1530
|
if (!live || live === pin.observed) return pin.openId;
|
|
1395
|
-
if (pin.observed === void 0) {
|
|
1396
|
-
openIdGlobal[PROCESS_OPEN_ID_SLOT] = {
|
|
1531
|
+
if (pin.observed === void 0 || pin.observedMinted) {
|
|
1532
|
+
openIdGlobal[PROCESS_OPEN_ID_SLOT] = {
|
|
1533
|
+
openId: pin.openId,
|
|
1534
|
+
observed: live,
|
|
1535
|
+
observedMinted: liveMinted
|
|
1536
|
+
};
|
|
1397
1537
|
return pin.openId;
|
|
1398
1538
|
}
|
|
1399
|
-
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId: live, observed: live };
|
|
1539
|
+
openIdGlobal[PROCESS_OPEN_ID_SLOT] = { openId: live, observed: live, observedMinted: liveMinted };
|
|
1400
1540
|
return live;
|
|
1401
1541
|
};
|
|
1402
1542
|
var bumpSessionCount = (storage, sessionsKey, openKey, openId = currentOpenId()) => {
|
|
@@ -1437,7 +1577,11 @@ var useQuestionnaireGate = ({
|
|
|
1437
1577
|
}) => {
|
|
1438
1578
|
const config = useStableValue2(configProp, shallowEqual);
|
|
1439
1579
|
const decision = useStableValue2(decisionProp, (a, b) => sameDecision(a, b));
|
|
1440
|
-
const
|
|
1580
|
+
const { flags: resolvedFeatures, settled: featuresSettled } = useResolvedFeaturesState({
|
|
1581
|
+
flags: features,
|
|
1582
|
+
config: featuresConfig
|
|
1583
|
+
});
|
|
1584
|
+
const featureEnabled = resolvedFeatures.questionnaire.enabled;
|
|
1441
1585
|
const testing = isTesting != null ? isTesting : isCoachmarkTesting();
|
|
1442
1586
|
const seenKey = questionnaireSeenKey(
|
|
1443
1587
|
config.id,
|
|
@@ -1446,11 +1590,22 @@ var useQuestionnaireGate = ({
|
|
|
1446
1590
|
const lastKey = questionnaireLastShownKey(config.id);
|
|
1447
1591
|
const sessionsKey = questionnaireSessionsKey(config.id);
|
|
1448
1592
|
const sessionOpenKey = questionnaireSessionOpenKey(config.id);
|
|
1449
|
-
const
|
|
1593
|
+
const openSessionId = React8.useSyncExternalStore(
|
|
1594
|
+
subscribeCurrentSessionId,
|
|
1595
|
+
getCurrentSessionId,
|
|
1596
|
+
getCurrentSessionId
|
|
1597
|
+
);
|
|
1598
|
+
const storageRef = React8.useRef(storage);
|
|
1599
|
+
storageRef.current = storage;
|
|
1600
|
+
const [sessions, setSessions] = React8.useState(() => {
|
|
1450
1601
|
const store = resolveStorage(storage);
|
|
1451
1602
|
warnMissingGateStorage(store, "questionnaire");
|
|
1452
1603
|
return bumpSessionCount(store, sessionsKey, sessionOpenKey);
|
|
1453
|
-
})
|
|
1604
|
+
});
|
|
1605
|
+
React8.useEffect(() => {
|
|
1606
|
+
const next = bumpSessionCount(resolveStorage(storageRef.current), sessionsKey, sessionOpenKey);
|
|
1607
|
+
setSessions((prev) => prev === next ? prev : next);
|
|
1608
|
+
}, [openSessionId, sessionsKey, sessionOpenKey]);
|
|
1454
1609
|
const hasServerDecision = decision != null;
|
|
1455
1610
|
const timeoutMs = config.timeoutFallbackMs;
|
|
1456
1611
|
const waits = !hasServerDecision && timeoutMs != null && timeoutMs > 0;
|
|
@@ -1484,10 +1639,11 @@ var useQuestionnaireGate = ({
|
|
|
1484
1639
|
});
|
|
1485
1640
|
const resolved = decideQuestionnaire(local, decision);
|
|
1486
1641
|
const alreadySeen = hasSeenGate(seenKey, storage, isTesting);
|
|
1487
|
-
const ready = hasServerDecision || elapsed;
|
|
1642
|
+
const ready = (hasServerDecision || elapsed) && featuresSettled;
|
|
1488
1643
|
return { visible: ready && resolved.fire && !alreadySeen, verdict: resolved };
|
|
1489
1644
|
}, [
|
|
1490
1645
|
featureEnabled,
|
|
1646
|
+
featuresSettled,
|
|
1491
1647
|
testing,
|
|
1492
1648
|
config,
|
|
1493
1649
|
decision,
|