@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
package/dist/showcase/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { forwardRef, createElement, createContext, useEffect, useMemo, useRef, useCallback,
|
|
1
|
+
import React, { forwardRef, createElement, createContext, useEffect, useMemo, useState, useRef, useCallback, useContext, useReducer } from 'react';
|
|
2
2
|
import { View, StyleSheet, AccessibilityInfo } from 'react-native';
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
|
|
@@ -383,9 +383,54 @@ var getCoachmarkStorage = () => coachmarkRuntime().storage;
|
|
|
383
383
|
var isCoachmarkTesting = () => coachmarkRuntime().testing;
|
|
384
384
|
var showcaseGateKey = (showcaseId) => `wire_showcase_${showcaseId}_seen`;
|
|
385
385
|
var SEEN_VALUE = "1";
|
|
386
|
+
var GATE_PROBE_KEY = "wire_gate_probe";
|
|
387
|
+
var ASYNC_GATE_STORAGE_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
|
|
388
|
+
"@wireai/activation:asyncGateStorageWarned"
|
|
389
|
+
);
|
|
390
|
+
var asyncGateWarnGlobal = globalThis;
|
|
391
|
+
var closedGateStorage = Object.freeze({
|
|
392
|
+
getItem: () => SEEN_VALUE,
|
|
393
|
+
setItem: () => {
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
var syncGateAdapters = /* @__PURE__ */ new WeakSet();
|
|
397
|
+
var asyncGateAdapters = /* @__PURE__ */ new WeakSet();
|
|
398
|
+
var warnAsyncGateStorage = () => {
|
|
399
|
+
if (asyncGateWarnGlobal[ASYNC_GATE_STORAGE_WARNED_SLOT]) return;
|
|
400
|
+
if (typeof __DEV__ === "undefined" || !__DEV__) return;
|
|
401
|
+
if (typeof console === "undefined" || !console.warn) return;
|
|
402
|
+
asyncGateWarnGlobal[ASYNC_GATE_STORAGE_WARNED_SLOT] = true;
|
|
403
|
+
console.warn(
|
|
404
|
+
"[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."
|
|
405
|
+
);
|
|
406
|
+
};
|
|
407
|
+
var validateGateStorage = (storage) => {
|
|
408
|
+
if (!storage) return null;
|
|
409
|
+
if (syncGateAdapters.has(storage)) return storage;
|
|
410
|
+
if (asyncGateAdapters.has(storage)) return closedGateStorage;
|
|
411
|
+
let probed;
|
|
412
|
+
try {
|
|
413
|
+
probed = storage.getItem(GATE_PROBE_KEY);
|
|
414
|
+
} catch {
|
|
415
|
+
syncGateAdapters.add(storage);
|
|
416
|
+
return storage;
|
|
417
|
+
}
|
|
418
|
+
if (probed != null && typeof probed.then === "function") {
|
|
419
|
+
asyncGateAdapters.add(storage);
|
|
420
|
+
try {
|
|
421
|
+
probed.then(void 0, () => {
|
|
422
|
+
});
|
|
423
|
+
} catch {
|
|
424
|
+
}
|
|
425
|
+
warnAsyncGateStorage();
|
|
426
|
+
return closedGateStorage;
|
|
427
|
+
}
|
|
428
|
+
syncGateAdapters.add(storage);
|
|
429
|
+
return storage;
|
|
430
|
+
};
|
|
386
431
|
var hasSeenGate = (key, storageOverride, testingOverride) => {
|
|
387
432
|
if (testingOverride != null ? testingOverride : isCoachmarkTesting()) return false;
|
|
388
|
-
const storage = storageOverride != null ? storageOverride : getCoachmarkStorage();
|
|
433
|
+
const storage = validateGateStorage(storageOverride != null ? storageOverride : getCoachmarkStorage());
|
|
389
434
|
if (!storage) return false;
|
|
390
435
|
try {
|
|
391
436
|
return storage.getItem(key) === SEEN_VALUE;
|
|
@@ -395,7 +440,7 @@ var hasSeenGate = (key, storageOverride, testingOverride) => {
|
|
|
395
440
|
};
|
|
396
441
|
var markSeenGate = (key, storageOverride, testingOverride) => {
|
|
397
442
|
if (testingOverride != null ? testingOverride : isCoachmarkTesting()) return;
|
|
398
|
-
const storage = storageOverride != null ? storageOverride : getCoachmarkStorage();
|
|
443
|
+
const storage = validateGateStorage(storageOverride != null ? storageOverride : getCoachmarkStorage());
|
|
399
444
|
if (!storage) return;
|
|
400
445
|
try {
|
|
401
446
|
storage.setItem(key, SEEN_VALUE);
|
|
@@ -483,17 +528,31 @@ var failOpen = async (storage, key) => {
|
|
|
483
528
|
const cached3 = await readCachedFeatures(storage, key);
|
|
484
529
|
return (_a = cached3 == null ? void 0 : cached3.features) != null ? _a : defaultWireFeatures;
|
|
485
530
|
};
|
|
486
|
-
var
|
|
531
|
+
var NO_ANSWER = /* @__PURE__ */ Symbol("wire-features-no-answer");
|
|
532
|
+
var fetchFeaturesJson = async (url, apiKey, timeoutMs) => {
|
|
487
533
|
const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
534
|
+
let timer;
|
|
535
|
+
const expired = new Promise((resolve) => {
|
|
536
|
+
timer = setTimeout(() => {
|
|
537
|
+
controller == null ? void 0 : controller.abort();
|
|
538
|
+
resolve(NO_ANSWER);
|
|
539
|
+
}, timeoutMs);
|
|
540
|
+
});
|
|
541
|
+
const exchange = (async () => {
|
|
542
|
+
const res = await fetch(url, {
|
|
491
543
|
method: "GET",
|
|
492
544
|
headers: { Accept: "application/json", Authorization: `Bearer ${apiKey}` },
|
|
493
545
|
signal: controller == null ? void 0 : controller.signal
|
|
494
546
|
});
|
|
547
|
+
if (!res || !res.ok) return NO_ANSWER;
|
|
548
|
+
return await res.json();
|
|
549
|
+
})();
|
|
550
|
+
try {
|
|
551
|
+
return await Promise.race([exchange, expired]);
|
|
495
552
|
} finally {
|
|
496
553
|
clearTimeout(timer);
|
|
554
|
+
void exchange.catch(() => {
|
|
555
|
+
});
|
|
497
556
|
}
|
|
498
557
|
};
|
|
499
558
|
var fetchWireFeatures = async (config) => {
|
|
@@ -503,9 +562,8 @@ var fetchWireFeatures = async (config) => {
|
|
|
503
562
|
const url = `${config.serverUrl.replace(/\/$/, "")}/v1/features`;
|
|
504
563
|
const timeoutMs = config.timeoutMs && config.timeoutMs > 0 ? config.timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
505
564
|
try {
|
|
506
|
-
const
|
|
507
|
-
if (
|
|
508
|
-
const json = await res.json();
|
|
565
|
+
const json = await fetchFeaturesJson(url, config.apiKey, timeoutMs);
|
|
566
|
+
if (json === NO_ANSWER) return failOpen(storage, key);
|
|
509
567
|
const features = parseWireFeatures(json);
|
|
510
568
|
if (storage) writeCachedFeatures(storage, key, features);
|
|
511
569
|
return features;
|
|
@@ -521,41 +579,56 @@ var useStableValue = (value, isEqual) => {
|
|
|
521
579
|
return ref.current;
|
|
522
580
|
};
|
|
523
581
|
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);
|
|
524
|
-
var
|
|
582
|
+
var useWireFeaturesState = (config) => {
|
|
525
583
|
const stableConfig = useStableValue(config, sameFetchKey);
|
|
526
584
|
const [flags, setFlags] = useState(defaultWireFeatures);
|
|
527
585
|
const canFetch = !!(stableConfig == null ? void 0 : stableConfig.serverUrl) && !!(stableConfig == null ? void 0 : stableConfig.apiKey);
|
|
586
|
+
const [settled, setSettled] = useState(!canFetch);
|
|
528
587
|
useEffect(() => {
|
|
529
588
|
if (!canFetch) {
|
|
530
589
|
setFlags((prev) => featuresEqual(prev, defaultWireFeatures) ? prev : defaultWireFeatures);
|
|
590
|
+
setSettled(true);
|
|
531
591
|
return;
|
|
532
592
|
}
|
|
533
593
|
let alive = true;
|
|
534
594
|
void fetchWireFeatures(stableConfig).then((next) => {
|
|
535
595
|
if (!alive) return;
|
|
536
596
|
setFlags((prev) => featuresEqual(prev, next) ? prev : next);
|
|
597
|
+
setSettled(true);
|
|
537
598
|
});
|
|
538
599
|
return () => {
|
|
539
600
|
alive = false;
|
|
540
601
|
};
|
|
541
602
|
}, [canFetch, stableConfig]);
|
|
542
|
-
return flags;
|
|
603
|
+
return useMemo(() => ({ flags, settled }), [flags, settled]);
|
|
543
604
|
};
|
|
544
605
|
var CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.context");
|
|
545
|
-
var
|
|
546
|
-
if (!
|
|
547
|
-
|
|
606
|
+
var featuresContextGlobal = globalThis;
|
|
607
|
+
if (!featuresContextGlobal[CONTEXT_SYMBOL]) {
|
|
608
|
+
featuresContextGlobal[CONTEXT_SYMBOL] = createContext(null);
|
|
548
609
|
}
|
|
549
|
-
var WireFeaturesContext =
|
|
610
|
+
var WireFeaturesContext = featuresContextGlobal[CONTEXT_SYMBOL];
|
|
611
|
+
var SETTLED_SYMBOL = /* @__PURE__ */ Symbol.for("wireai.features.settled.context");
|
|
612
|
+
var settledContextGlobal = globalThis;
|
|
613
|
+
if (!settledContextGlobal[SETTLED_SYMBOL]) {
|
|
614
|
+
settledContextGlobal[SETTLED_SYMBOL] = createContext(true);
|
|
615
|
+
}
|
|
616
|
+
var WireFeaturesSettledContext = settledContextGlobal[SETTLED_SYMBOL];
|
|
550
617
|
var useWireFeaturesContext = () => useContext(WireFeaturesContext);
|
|
551
|
-
var
|
|
618
|
+
var useResolvedFeaturesState = (options) => {
|
|
552
619
|
var _a, _b, _c;
|
|
553
620
|
const ctx = useWireFeaturesContext();
|
|
621
|
+
const ctxSettled = useContext(WireFeaturesSettledContext);
|
|
554
622
|
const shouldFetch = !(options == null ? void 0 : options.flags) && ctx == null;
|
|
555
|
-
const fetched =
|
|
556
|
-
|
|
623
|
+
const fetched = useWireFeaturesState(shouldFetch ? options == null ? void 0 : options.config : void 0);
|
|
624
|
+
const flags = (_c = (_b = (_a = options == null ? void 0 : options.flags) != null ? _a : ctx) != null ? _b : fetched.flags) != null ? _c : defaultWireFeatures;
|
|
625
|
+
const settled = (options == null ? void 0 : options.flags) ? true : ctx != null ? ctxSettled : fetched.settled;
|
|
626
|
+
return useMemo(() => ({ flags, settled }), [flags, settled]);
|
|
557
627
|
};
|
|
558
628
|
|
|
629
|
+
// src/session/persistedSession.ts
|
|
630
|
+
var READ_TIMEOUT_MS2 = 1500;
|
|
631
|
+
|
|
559
632
|
// src/theme/defaultTheme.ts
|
|
560
633
|
var defaultOnboardingTheme = {
|
|
561
634
|
colors: {
|
|
@@ -715,7 +788,10 @@ var _FeatureShowcase = ({
|
|
|
715
788
|
[active, themeOverride]
|
|
716
789
|
);
|
|
717
790
|
const accent = accentColor != null ? accentColor : t.colors.primary;
|
|
718
|
-
const
|
|
791
|
+
const { flags, settled } = useResolvedFeaturesState({
|
|
792
|
+
flags: features,
|
|
793
|
+
config: featuresConfig
|
|
794
|
+
});
|
|
719
795
|
const disabled = !flags.showcase.enabled;
|
|
720
796
|
const Onboarding = useMemo(() => resolveShowcaseOnboarding(), []);
|
|
721
797
|
const gateKey = showcaseGateKey(config.id);
|
|
@@ -724,6 +800,14 @@ var _FeatureShowcase = ({
|
|
|
724
800
|
[gateKey, storage, isTesting]
|
|
725
801
|
);
|
|
726
802
|
const skip = seen || disabled || !Onboarding;
|
|
803
|
+
const flagsCouldMatter = !seen && !!Onboarding;
|
|
804
|
+
const [holdExpired, setHoldExpired] = useState(false);
|
|
805
|
+
useEffect(() => {
|
|
806
|
+
if (settled || holdExpired || !flagsCouldMatter) return void 0;
|
|
807
|
+
const timer = setTimeout(() => setHoldExpired(true), READ_TIMEOUT_MS2);
|
|
808
|
+
return () => clearTimeout(timer);
|
|
809
|
+
}, [settled, holdExpired, flagsCouldMatter]);
|
|
810
|
+
const holding = flagsCouldMatter && !settled && !holdExpired;
|
|
727
811
|
const doneRef = useRef(false);
|
|
728
812
|
const finish = useCallback(() => {
|
|
729
813
|
if (doneRef.current) return;
|
|
@@ -732,11 +816,12 @@ var _FeatureShowcase = ({
|
|
|
732
816
|
onDone();
|
|
733
817
|
}, [gateKey, storage, isTesting, onDone]);
|
|
734
818
|
useEffect(() => {
|
|
819
|
+
if (holding) return;
|
|
735
820
|
if (skip && !doneRef.current) {
|
|
736
821
|
doneRef.current = true;
|
|
737
822
|
onDone();
|
|
738
823
|
}
|
|
739
|
-
}, [skip, onDone]);
|
|
824
|
+
}, [holding, skip, onDone]);
|
|
740
825
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
741
826
|
const steps = useMemo(
|
|
742
827
|
() => config.slides.map((slide) => {
|
|
@@ -803,6 +888,7 @@ var _FeatureShowcase = ({
|
|
|
803
888
|
}),
|
|
804
889
|
[t]
|
|
805
890
|
);
|
|
891
|
+
if (holding) return null;
|
|
806
892
|
if (skip || !Onboarding) return null;
|
|
807
893
|
const activeGesture = (_a = config.slides[activeIndex]) == null ? void 0 : _a.gesture;
|
|
808
894
|
const showGesture = !!activeGesture && activeGesture !== "none" && activeGesture !== "tap" && activeGesture !== "double_tap";
|