@wireai/activation 0.12.2 → 0.13.2
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 +3 -1
- package/CHANGELOG.md +259 -1
- package/README.md +87 -3
- package/dist/analytics/index.d.mts +2 -2
- package/dist/analytics/index.d.ts +2 -2
- package/dist/analytics/index.js +174 -36
- package/dist/analytics/index.js.map +1 -1
- package/dist/analytics/index.mjs +174 -37
- package/dist/analytics/index.mjs.map +1 -1
- package/dist/{currentSession-BlCeDP0f.d.mts → currentSession-ClkLjcJ0.d.mts} +456 -19
- package/dist/{currentSession-BxEB37xt.d.ts → currentSession-DOVZEWJl.d.ts} +456 -19
- package/dist/index.d.mts +197 -16
- package/dist/index.d.ts +197 -16
- package/dist/index.js +1056 -390
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +836 -193
- package/dist/index.mjs.map +1 -1
- package/dist/questionnaire/index.js.map +1 -1
- package/dist/questionnaire/index.mjs.map +1 -1
- package/dist/reviews/index.js +20 -7
- package/dist/reviews/index.js.map +1 -1
- package/dist/reviews/index.mjs +20 -7
- package/dist/reviews/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/OnboardingFlow.tsx +141 -3
- package/src/WireOnboarding.tsx +178 -34
- package/src/activation/wireActivation.ts +13 -7
- package/src/analytics/analyticsEvent.ts +16 -1
- package/src/analytics/analyticsFacade.ts +11 -10
- package/src/analytics/currentSession.ts +6 -20
- package/src/analytics/eventQueue.ts +71 -1
- package/src/analytics/index.ts +1 -1
- package/src/analytics/reportClientEvent.ts +157 -38
- package/src/cards/PermissionCard.tsx +438 -0
- package/src/cards/index.ts +7 -0
- package/src/config/wireConfigFromEnv.ts +1 -10
- package/src/context/deviceId.ts +77 -16
- package/src/context/userContext.ts +4 -15
- package/src/identity/identityRecord.ts +123 -0
- package/src/identity/userIdentity.ts +45 -9
- package/src/illustrations/defaultIllustrations.tsx +44 -3
- package/src/index.ts +44 -4
- package/src/permissions/index.ts +64 -0
- package/src/permissions/permissionCopy.ts +87 -0
- package/src/permissions/permissionEvents.ts +76 -0
- package/src/permissions/permissionMemory.ts +88 -0
- package/src/permissions/placement.ts +88 -0
- package/src/permissions/types.ts +131 -0
- package/src/session/persistedSession.ts +10 -3
- package/src/session-analytics/useLifecycleEvents.ts +10 -1
- package/src/types.ts +77 -1
- package/src/utils/deriveAnswers.ts +6 -2
- package/src/utils/readProgress.ts +4 -0
- package/src/utils/warnInDev.ts +33 -0
- package/src/components/DoneBlock.tsx +0 -37
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { StyleSheet, Easing, Animated, BackHandler, Modal, Pressable, Text, View, TouchableOpacity, KeyboardAvoidingView, ScrollView, useWindowDimensions, TextInput, Image, AccessibilityInfo, Platform, Dimensions, I18nManager, AppState, NativeModules, TurboModuleRegistry } from 'react-native';
|
|
2
|
-
import
|
|
2
|
+
import React19, { createContext, forwardRef, useRef, useState, useEffect, useCallback, useImperativeHandle, useMemo, useContext, useSyncExternalStore } from 'react';
|
|
3
3
|
import { useWireAIThread, useWireAIAction, ComponentRenderer, WireAIProvider } from 'wireai-rn';
|
|
4
4
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
@@ -484,7 +484,7 @@ var useIllustration = (name2) => {
|
|
|
484
484
|
if (!name2) return void 0;
|
|
485
485
|
return registry[name2];
|
|
486
486
|
};
|
|
487
|
-
var
|
|
487
|
+
var Disc = ({ children }) => {
|
|
488
488
|
const t = useOnboardingTheme();
|
|
489
489
|
return /* @__PURE__ */ jsx(
|
|
490
490
|
View,
|
|
@@ -493,11 +493,23 @@ var Glyph = ({ children }) => {
|
|
|
493
493
|
styles.disc,
|
|
494
494
|
{ backgroundColor: t.colors.primarySoft, borderRadius: t.radius.full }
|
|
495
495
|
],
|
|
496
|
-
children
|
|
496
|
+
children
|
|
497
497
|
}
|
|
498
498
|
);
|
|
499
499
|
};
|
|
500
|
+
var Glyph = ({ children }) => {
|
|
501
|
+
const t = useOnboardingTheme();
|
|
502
|
+
return /* @__PURE__ */ jsx(Disc, { children: /* @__PURE__ */ jsx(Text, { style: [styles.glyphText, { color: t.colors.primary }], allowFontScaling: false, children }) });
|
|
503
|
+
};
|
|
500
504
|
var MomentumGlyph = () => /* @__PURE__ */ jsx(Glyph, { children: "\u2197" });
|
|
505
|
+
var NotificationsGlyph = () => {
|
|
506
|
+
const t = useOnboardingTheme();
|
|
507
|
+
return /* @__PURE__ */ jsx(Disc, { children: /* @__PURE__ */ jsxs(View, { style: styles.bell, children: [
|
|
508
|
+
/* @__PURE__ */ jsx(View, { style: [styles.bellBody, { backgroundColor: t.colors.primary }] }),
|
|
509
|
+
/* @__PURE__ */ jsx(View, { style: [styles.bellRim, { backgroundColor: t.colors.primary }] }),
|
|
510
|
+
/* @__PURE__ */ jsx(View, { style: [styles.bellClapper, { backgroundColor: t.colors.primary }] })
|
|
511
|
+
] }) });
|
|
512
|
+
};
|
|
501
513
|
var BeforeAfterGlyph = () => {
|
|
502
514
|
const t = useOnboardingTheme();
|
|
503
515
|
return /* @__PURE__ */ jsxs(View, { style: styles.row, children: [
|
|
@@ -524,7 +536,8 @@ var BeforeAfterGlyph = () => {
|
|
|
524
536
|
};
|
|
525
537
|
var defaultIllustrations = {
|
|
526
538
|
momentum: /* @__PURE__ */ jsx(MomentumGlyph, {}),
|
|
527
|
-
"before-after": /* @__PURE__ */ jsx(BeforeAfterGlyph, {})
|
|
539
|
+
"before-after": /* @__PURE__ */ jsx(BeforeAfterGlyph, {}),
|
|
540
|
+
notifications: /* @__PURE__ */ jsx(NotificationsGlyph, {})
|
|
528
541
|
};
|
|
529
542
|
var styles = StyleSheet.create({
|
|
530
543
|
disc: {
|
|
@@ -552,7 +565,12 @@ var styles = StyleSheet.create({
|
|
|
552
565
|
arrow: {
|
|
553
566
|
fontSize: 28,
|
|
554
567
|
fontWeight: "700"
|
|
555
|
-
}
|
|
568
|
+
},
|
|
569
|
+
// The bell: a domed body over a wider rim, with the clapper hanging below it.
|
|
570
|
+
bell: { alignItems: "center", justifyContent: "center" },
|
|
571
|
+
bellBody: { width: 34, height: 30, borderTopLeftRadius: 17, borderTopRightRadius: 17 },
|
|
572
|
+
bellRim: { width: 44, height: 5, borderRadius: 3, marginTop: 2 },
|
|
573
|
+
bellClapper: { width: 9, height: 9, borderRadius: 5, marginTop: 3 }
|
|
556
574
|
});
|
|
557
575
|
var EMPTY_REGISTRY2 = {};
|
|
558
576
|
var IconContext = createContext(EMPTY_REGISTRY2);
|
|
@@ -802,7 +820,7 @@ var _AnimatedSparkle = ({ size = 30, color, variant = "spin" }) => {
|
|
|
802
820
|
}
|
|
803
821
|
) });
|
|
804
822
|
};
|
|
805
|
-
var AnimatedSparkle =
|
|
823
|
+
var AnimatedSparkle = React19.memo(_AnimatedSparkle);
|
|
806
824
|
var styles2 = StyleSheet.create({
|
|
807
825
|
box: { alignItems: "center", justifyContent: "center" },
|
|
808
826
|
glyph: {
|
|
@@ -905,7 +923,7 @@ var _LoadingBlock = ({
|
|
|
905
923
|
)
|
|
906
924
|
] });
|
|
907
925
|
};
|
|
908
|
-
var LoadingBlock =
|
|
926
|
+
var LoadingBlock = React19.memo(_LoadingBlock);
|
|
909
927
|
var styles3 = StyleSheet.create({
|
|
910
928
|
center: { flex: 1, alignItems: "center", justifyContent: "center" },
|
|
911
929
|
// Absolutely-positioned label column, pinned just under the centered stage: its top
|
|
@@ -950,7 +968,7 @@ var _LoadingScreen = ({ title, hint }) => {
|
|
|
950
968
|
}
|
|
951
969
|
);
|
|
952
970
|
};
|
|
953
|
-
var LoadingScreen =
|
|
971
|
+
var LoadingScreen = React19.memo(_LoadingScreen);
|
|
954
972
|
var styles4 = StyleSheet.create({
|
|
955
973
|
flex: { flex: 1 }
|
|
956
974
|
});
|
|
@@ -990,7 +1008,7 @@ var _StepProgress = ({ step, complete, approxScreens }) => {
|
|
|
990
1008
|
}
|
|
991
1009
|
) });
|
|
992
1010
|
};
|
|
993
|
-
var StepProgress =
|
|
1011
|
+
var StepProgress = React19.memo(_StepProgress);
|
|
994
1012
|
var styles5 = StyleSheet.create({
|
|
995
1013
|
track: { height: 6, width: "100%", overflow: "hidden" },
|
|
996
1014
|
fill: { height: 6 }
|
|
@@ -1030,7 +1048,7 @@ var _OnboardingScaffold = ({
|
|
|
1030
1048
|
}
|
|
1031
1049
|
);
|
|
1032
1050
|
};
|
|
1033
|
-
var OnboardingScaffold =
|
|
1051
|
+
var OnboardingScaffold = React19.memo(_OnboardingScaffold);
|
|
1034
1052
|
function makeStyles(t) {
|
|
1035
1053
|
return StyleSheet.create({
|
|
1036
1054
|
flex: { flex: 1 },
|
|
@@ -1131,7 +1149,7 @@ var _Button = ({
|
|
|
1131
1149
|
}
|
|
1132
1150
|
);
|
|
1133
1151
|
};
|
|
1134
|
-
var Button =
|
|
1152
|
+
var Button = React19.memo(_Button);
|
|
1135
1153
|
var styles6 = StyleSheet.create({
|
|
1136
1154
|
base: { alignItems: "center", justifyContent: "center" },
|
|
1137
1155
|
fill: { width: "100%" }
|
|
@@ -1149,7 +1167,7 @@ var _ErrorBlock = ({
|
|
|
1149
1167
|
onRetry ? /* @__PURE__ */ jsx(Button, { title: retryLabel, onPress: onRetry, variant: "outline" }) : null
|
|
1150
1168
|
] });
|
|
1151
1169
|
};
|
|
1152
|
-
var ErrorBlock =
|
|
1170
|
+
var ErrorBlock = React19.memo(_ErrorBlock);
|
|
1153
1171
|
var styles7 = StyleSheet.create({
|
|
1154
1172
|
center: { flex: 1, alignItems: "center", justifyContent: "center" }
|
|
1155
1173
|
});
|
|
@@ -1195,7 +1213,7 @@ var _CardLayout = ({
|
|
|
1195
1213
|
footer ? /* @__PURE__ */ jsx(View, { style: styles18.footer, children: footer }) : null
|
|
1196
1214
|
] });
|
|
1197
1215
|
};
|
|
1198
|
-
var CardLayout =
|
|
1216
|
+
var CardLayout = React19.memo(_CardLayout);
|
|
1199
1217
|
function makeStyles2(t) {
|
|
1200
1218
|
return StyleSheet.create({
|
|
1201
1219
|
fill: { flex: 1 },
|
|
@@ -1320,7 +1338,7 @@ var _CompletionView = ({
|
|
|
1320
1338
|
}
|
|
1321
1339
|
);
|
|
1322
1340
|
};
|
|
1323
|
-
var CompletionView =
|
|
1341
|
+
var CompletionView = React19.memo(_CompletionView);
|
|
1324
1342
|
var styles8 = StyleSheet.create({
|
|
1325
1343
|
center: { width: "100%", alignItems: "center", justifyContent: "center" },
|
|
1326
1344
|
badgeStage: {
|
|
@@ -1450,7 +1468,7 @@ var transformFor = (variant, t, reduced, layer, travel) => {
|
|
|
1450
1468
|
}
|
|
1451
1469
|
];
|
|
1452
1470
|
};
|
|
1453
|
-
var CardHandoff =
|
|
1471
|
+
var CardHandoff = React19.memo(_CardHandoff);
|
|
1454
1472
|
var styles9 = StyleSheet.create({
|
|
1455
1473
|
fill: { flex: 1 }
|
|
1456
1474
|
});
|
|
@@ -1474,7 +1492,7 @@ var deriveAnswers = (messages) => {
|
|
|
1474
1492
|
const props = (_a2 = r.props) != null ? _a2 : {};
|
|
1475
1493
|
const progress = props.progress;
|
|
1476
1494
|
const questionText = (_c = (_b = props.title) != null ? _b : props.label) != null ? _c : props.question;
|
|
1477
|
-
const key = (progress == null ? void 0 : progress.key) || questionText;
|
|
1495
|
+
const key = (progress == null ? void 0 : progress.slot_id) || (progress == null ? void 0 : progress.key) || questionText;
|
|
1478
1496
|
const reply = messages[i + 1];
|
|
1479
1497
|
if (key && (reply == null ? void 0 : reply.role) === "user") {
|
|
1480
1498
|
out[key] = tryParse(stripPrefix(reply.content));
|
|
@@ -1495,45 +1513,71 @@ var readProgress = (response) => {
|
|
|
1495
1513
|
step: typeof p.step === "number" ? p.step : void 0,
|
|
1496
1514
|
total: typeof p.total === "number" ? p.total : void 0,
|
|
1497
1515
|
key: typeof p.key === "string" ? p.key : void 0,
|
|
1516
|
+
// Whitelisted the same way as every other field: an old backend simply omits it.
|
|
1517
|
+
slot_id: typeof p.slot_id === "string" ? p.slot_id : void 0,
|
|
1498
1518
|
skippable: typeof p.skippable === "boolean" ? p.skippable : void 0
|
|
1499
1519
|
};
|
|
1500
1520
|
};
|
|
1501
1521
|
|
|
1502
1522
|
// src/analytics/reportClientEvent.ts
|
|
1503
1523
|
var makeSessionId = () => `wire_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
1524
|
+
var toWireEvents = (events) => events.map((event) => {
|
|
1525
|
+
if (typeof event.ts !== "number") return event;
|
|
1526
|
+
const { ts, ...rest } = event;
|
|
1527
|
+
if (!Number.isFinite(ts)) return rest;
|
|
1528
|
+
try {
|
|
1529
|
+
return { ...rest, ts: new Date(ts).toISOString() };
|
|
1530
|
+
} catch {
|
|
1531
|
+
return rest;
|
|
1532
|
+
}
|
|
1533
|
+
});
|
|
1504
1534
|
var buildEventsRequest = (target, events) => {
|
|
1505
1535
|
if (!(target == null ? void 0 : target.serverUrl) || events.length === 0) return null;
|
|
1506
1536
|
try {
|
|
1507
1537
|
const url = `${target.serverUrl.replace(/\/$/, "")}/v1/events`;
|
|
1508
1538
|
const headers = { "Content-Type": "application/json" };
|
|
1509
1539
|
if (target.apiKey) headers.Authorization = `Bearer ${target.apiKey}`;
|
|
1510
|
-
|
|
1540
|
+
const body = JSON.stringify({ events: toWireEvents(events) });
|
|
1541
|
+
return { url, init: { method: "POST", headers, body } };
|
|
1511
1542
|
} catch {
|
|
1512
1543
|
return null;
|
|
1513
1544
|
}
|
|
1514
1545
|
};
|
|
1515
|
-
var
|
|
1516
|
-
if (typeof __DEV__ === "undefined" || !__DEV__) return;
|
|
1517
|
-
if (typeof console === "undefined" || !console.warn) return;
|
|
1546
|
+
var readEventsAck = async (res) => {
|
|
1518
1547
|
try {
|
|
1519
1548
|
const json = res == null ? void 0 : res.json;
|
|
1520
|
-
if (typeof json !== "function") return;
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1549
|
+
if (typeof json !== "function") return void 0;
|
|
1550
|
+
const body = await Promise.resolve(json.call(res));
|
|
1551
|
+
const skipped = body == null ? void 0 : body.skipped;
|
|
1552
|
+
if (typeof skipped !== "number" || !Number.isFinite(skipped)) return void 0;
|
|
1553
|
+
const reasons = Array.isArray(body == null ? void 0 : body.errors) ? body.errors.map((e) => {
|
|
1554
|
+
const entry = e;
|
|
1555
|
+
const reason = entry == null ? void 0 : entry.reason;
|
|
1556
|
+
if (typeof reason !== "string") return void 0;
|
|
1557
|
+
const field = entry == null ? void 0 : entry.field;
|
|
1558
|
+
return typeof field === "string" && field.length > 0 ? `${reason} (field: ${field})` : reason;
|
|
1559
|
+
}).filter((r) => typeof r === "string") : [];
|
|
1560
|
+
return { written: typeof (body == null ? void 0 : body.written) === "number" ? body.written : void 0, skipped, reasons };
|
|
1529
1561
|
} catch {
|
|
1562
|
+
return void 0;
|
|
1530
1563
|
}
|
|
1531
1564
|
};
|
|
1565
|
+
var describeDiscarded = (ack) => `[wireai] the server ACCEPTED the /v1/events POST but DISCARDED ${ack.skipped} event(s) (skipped in the response body) \u2014 they are gone, not retried. The server reported: skipped=${ack.skipped}${ack.written !== void 0 ? `, written=${ack.written}` : ""}` + (ack.reasons.length > 0 ? `, reasons: ${ack.reasons.join(", ")}.` : ". It gave no reason (the endpoint folds every rejection into one count), so check the server's ingest log for this request rather than guessing.");
|
|
1566
|
+
var warnOnSkippedEvents = (res) => {
|
|
1567
|
+
if (typeof __DEV__ === "undefined" || !__DEV__) return;
|
|
1568
|
+
if (typeof console === "undefined" || !console.warn) return;
|
|
1569
|
+
void readEventsAck(res).then((ack) => {
|
|
1570
|
+
if (!ack || ack.skipped <= 0) return;
|
|
1571
|
+
console.warn(describeDiscarded(ack));
|
|
1572
|
+
});
|
|
1573
|
+
};
|
|
1532
1574
|
var reportClientEvents = (target, events) => {
|
|
1533
1575
|
try {
|
|
1534
1576
|
const req = buildEventsRequest(target, events);
|
|
1535
1577
|
if (!req) return;
|
|
1536
|
-
void fetch(req.url, req.init).
|
|
1578
|
+
void fetch(req.url, req.init).then((res) => {
|
|
1579
|
+
warnOnSkippedEvents(res);
|
|
1580
|
+
}).catch(() => {
|
|
1537
1581
|
});
|
|
1538
1582
|
} catch {
|
|
1539
1583
|
}
|
|
@@ -1544,7 +1588,13 @@ var reportClientEventsAwait = async (target, events) => {
|
|
|
1544
1588
|
const req = buildEventsRequest(target, events);
|
|
1545
1589
|
if (!req) return false;
|
|
1546
1590
|
const res = await fetch(req.url, req.init);
|
|
1547
|
-
|
|
1591
|
+
if (!res || !res.ok) return false;
|
|
1592
|
+
const ack = await readEventsAck(res);
|
|
1593
|
+
if (!ack || ack.skipped <= 0) return true;
|
|
1594
|
+
if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
|
|
1595
|
+
console.warn(describeDiscarded(ack));
|
|
1596
|
+
}
|
|
1597
|
+
return false;
|
|
1548
1598
|
} catch {
|
|
1549
1599
|
return false;
|
|
1550
1600
|
}
|
|
@@ -1576,6 +1626,382 @@ var sendPreview = (target, { sessionId, userMessage }) => {
|
|
|
1576
1626
|
} catch {
|
|
1577
1627
|
}
|
|
1578
1628
|
};
|
|
1629
|
+
|
|
1630
|
+
// src/permissions/permissionEvents.ts
|
|
1631
|
+
var WIRE_PERMISSION_EVENTS = {
|
|
1632
|
+
/** The priming screen became visible. The denominator for every rate below. */
|
|
1633
|
+
screenShown: "wire_permission_screen_shown",
|
|
1634
|
+
/** The user tapped the primary, so the OS dialog is about to open. The rationale worked. */
|
|
1635
|
+
primerAccepted: "wire_permission_primer_accepted",
|
|
1636
|
+
/** The OS granted it. */
|
|
1637
|
+
granted: "wire_permission_granted",
|
|
1638
|
+
/** The OS refused it (a permanently blocked answer reports here too, with `status: "blocked"`). */
|
|
1639
|
+
denied: "wire_permission_denied",
|
|
1640
|
+
/** The user took the secondary. The one native prompt was NOT spent. */
|
|
1641
|
+
skipped: "wire_permission_skipped",
|
|
1642
|
+
/** A blocked user was redirected to the OS settings page. */
|
|
1643
|
+
settingsOpened: "wire_permission_settings_opened"
|
|
1644
|
+
};
|
|
1645
|
+
var permissionEventName = (stage) => {
|
|
1646
|
+
switch (stage) {
|
|
1647
|
+
case "shown":
|
|
1648
|
+
return WIRE_PERMISSION_EVENTS.screenShown;
|
|
1649
|
+
case "accepted":
|
|
1650
|
+
return WIRE_PERMISSION_EVENTS.primerAccepted;
|
|
1651
|
+
case "granted":
|
|
1652
|
+
return WIRE_PERMISSION_EVENTS.granted;
|
|
1653
|
+
case "denied":
|
|
1654
|
+
return WIRE_PERMISSION_EVENTS.denied;
|
|
1655
|
+
case "skipped":
|
|
1656
|
+
return WIRE_PERMISSION_EVENTS.skipped;
|
|
1657
|
+
case "settings":
|
|
1658
|
+
return WIRE_PERMISSION_EVENTS.settingsOpened;
|
|
1659
|
+
default: {
|
|
1660
|
+
const _exhaustive = stage;
|
|
1661
|
+
return _exhaustive;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
};
|
|
1665
|
+
var permissionEventProps = (permission, status) => status ? { permission, status } : { permission };
|
|
1666
|
+
var normalizePermissionStatus = (value) => value === "granted" || value === "denied" || value === "blocked" ? value : "denied";
|
|
1667
|
+
|
|
1668
|
+
// src/haptics/haptics.ts
|
|
1669
|
+
var play = async (tone) => {
|
|
1670
|
+
try {
|
|
1671
|
+
const mod = await import('expo-haptics');
|
|
1672
|
+
if (!mod) return false;
|
|
1673
|
+
if (tone === "selection") ;
|
|
1674
|
+
if (tone === "light") ;
|
|
1675
|
+
await mod.notificationAsync(mod.NotificationFeedbackType.Success);
|
|
1676
|
+
return true;
|
|
1677
|
+
} catch {
|
|
1678
|
+
return false;
|
|
1679
|
+
}
|
|
1680
|
+
};
|
|
1681
|
+
var playHaptic = (tone) => {
|
|
1682
|
+
void play(tone);
|
|
1683
|
+
};
|
|
1684
|
+
|
|
1685
|
+
// src/utils/warnInDev.ts
|
|
1686
|
+
var warnInDev = (message) => {
|
|
1687
|
+
if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
|
|
1688
|
+
console.warn(message);
|
|
1689
|
+
return true;
|
|
1690
|
+
}
|
|
1691
|
+
return false;
|
|
1692
|
+
};
|
|
1693
|
+
var easeWire4 = Easing.bezier(...WIRE_BEZIER);
|
|
1694
|
+
var RISE_PX = 10;
|
|
1695
|
+
var PERMISSION_CARD_NAME = "PermissionCard";
|
|
1696
|
+
var REQUEST_WATCHDOG_MS = 9e4;
|
|
1697
|
+
var schema = z.object({
|
|
1698
|
+
permission: z.string().describe("Which OS permission this screen primes, e.g. 'notifications'"),
|
|
1699
|
+
title: z.string().describe("Headline for the ask"),
|
|
1700
|
+
message: z.string().describe("Why the app needs it, in the user's terms"),
|
|
1701
|
+
primaryLabel: z.string().describe("Primary button, the only control that can open the OS dialog"),
|
|
1702
|
+
secondaryLabel: z.string().describe("Secondary button, advances without spending the OS prompt"),
|
|
1703
|
+
blockedTitle: z.string().optional().describe("Headline once the permission is permanently refused"),
|
|
1704
|
+
blockedMessage: z.string().optional().describe("Rationale for the settings route"),
|
|
1705
|
+
settingsLabel: z.string().optional().describe("Primary button label on the blocked route"),
|
|
1706
|
+
continueLabel: z.string().optional().describe("Primary button label when there is nothing to ask"),
|
|
1707
|
+
illustration: z.string().optional().describe("Name of an app-provided illustration (defaults to the permission name)")
|
|
1708
|
+
});
|
|
1709
|
+
var _PermissionCard = ({
|
|
1710
|
+
permission,
|
|
1711
|
+
title,
|
|
1712
|
+
message,
|
|
1713
|
+
primaryLabel,
|
|
1714
|
+
secondaryLabel,
|
|
1715
|
+
blockedTitle,
|
|
1716
|
+
blockedMessage,
|
|
1717
|
+
settingsLabel,
|
|
1718
|
+
continueLabel,
|
|
1719
|
+
illustration,
|
|
1720
|
+
request,
|
|
1721
|
+
getStatus,
|
|
1722
|
+
openSettings,
|
|
1723
|
+
onStage,
|
|
1724
|
+
onSettled
|
|
1725
|
+
}) => {
|
|
1726
|
+
const t = useOnboardingTheme();
|
|
1727
|
+
const reduced = useReducedMotion();
|
|
1728
|
+
const art = useIllustration(illustration != null ? illustration : permission);
|
|
1729
|
+
const [status, setStatus] = useState(void 0);
|
|
1730
|
+
const [busy, setBusy] = useState(false);
|
|
1731
|
+
const settledRef = useRef(false);
|
|
1732
|
+
const acceptedRef = useRef(false);
|
|
1733
|
+
const watchdogRef = useRef(null);
|
|
1734
|
+
const clearWatchdog = useCallback(() => {
|
|
1735
|
+
if (watchdogRef.current) {
|
|
1736
|
+
clearTimeout(watchdogRef.current);
|
|
1737
|
+
watchdogRef.current = null;
|
|
1738
|
+
}
|
|
1739
|
+
}, []);
|
|
1740
|
+
useEffect(() => clearWatchdog, [clearWatchdog]);
|
|
1741
|
+
const onStageRef = useRef(onStage);
|
|
1742
|
+
onStageRef.current = onStage;
|
|
1743
|
+
const onSettledRef = useRef(onSettled);
|
|
1744
|
+
onSettledRef.current = onSettled;
|
|
1745
|
+
const getStatusRef = useRef(getStatus);
|
|
1746
|
+
getStatusRef.current = getStatus;
|
|
1747
|
+
const shownRef = useRef(false);
|
|
1748
|
+
useEffect(() => {
|
|
1749
|
+
var _a2;
|
|
1750
|
+
if (shownRef.current) return;
|
|
1751
|
+
shownRef.current = true;
|
|
1752
|
+
(_a2 = onStageRef.current) == null ? void 0 : _a2.call(onStageRef, "shown");
|
|
1753
|
+
}, []);
|
|
1754
|
+
useEffect(() => {
|
|
1755
|
+
const probe = getStatusRef.current;
|
|
1756
|
+
if (!probe) return;
|
|
1757
|
+
let cancelled = false;
|
|
1758
|
+
try {
|
|
1759
|
+
void Promise.resolve(probe()).then((value) => {
|
|
1760
|
+
if (!cancelled) setStatus(normalizePermissionStatus(value));
|
|
1761
|
+
}).catch(() => {
|
|
1762
|
+
});
|
|
1763
|
+
} catch {
|
|
1764
|
+
}
|
|
1765
|
+
return () => {
|
|
1766
|
+
cancelled = true;
|
|
1767
|
+
};
|
|
1768
|
+
}, []);
|
|
1769
|
+
const requestRef = useRef(request);
|
|
1770
|
+
requestRef.current = request;
|
|
1771
|
+
useEffect(() => {
|
|
1772
|
+
if (requestRef.current) return;
|
|
1773
|
+
warnInDev(
|
|
1774
|
+
`[wireai] <WireOnboarding> got a permission screen for "${permission}" with no \`request\` function, so it cannot open the OS dialog and renders as a plain continue. Pass request: () => Promise<'granted' | 'denied' | 'blocked'> (5 lines around expo-notifications, see the README).`
|
|
1775
|
+
);
|
|
1776
|
+
}, [permission]);
|
|
1777
|
+
const settle = useCallback((outcome) => {
|
|
1778
|
+
var _a2;
|
|
1779
|
+
if (settledRef.current) return;
|
|
1780
|
+
settledRef.current = true;
|
|
1781
|
+
clearWatchdog();
|
|
1782
|
+
(_a2 = onSettledRef.current) == null ? void 0 : _a2.call(onSettledRef, outcome);
|
|
1783
|
+
}, [clearWatchdog]);
|
|
1784
|
+
const primaryAction = status === "blocked" ? "settings" : status === "granted" || !request ? "continue" : "ask";
|
|
1785
|
+
const handlePrimary = useCallback(() => {
|
|
1786
|
+
var _a2, _b, _c, _d;
|
|
1787
|
+
if (settledRef.current || busy) return;
|
|
1788
|
+
if (primaryAction === "settings") {
|
|
1789
|
+
(_a2 = onStageRef.current) == null ? void 0 : _a2.call(onStageRef, "settings", "blocked");
|
|
1790
|
+
try {
|
|
1791
|
+
void Promise.resolve(openSettings == null ? void 0 : openSettings()).catch(() => {
|
|
1792
|
+
});
|
|
1793
|
+
} catch {
|
|
1794
|
+
}
|
|
1795
|
+
settle("blocked");
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
if (primaryAction === "continue") {
|
|
1799
|
+
if (status === "granted") {
|
|
1800
|
+
(_b = onStageRef.current) == null ? void 0 : _b.call(onStageRef, "granted", "granted");
|
|
1801
|
+
settle("granted");
|
|
1802
|
+
} else {
|
|
1803
|
+
(_c = onStageRef.current) == null ? void 0 : _c.call(onStageRef, "skipped");
|
|
1804
|
+
settle("skipped");
|
|
1805
|
+
}
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
if (!acceptedRef.current) {
|
|
1809
|
+
acceptedRef.current = true;
|
|
1810
|
+
(_d = onStageRef.current) == null ? void 0 : _d.call(onStageRef, "accepted");
|
|
1811
|
+
}
|
|
1812
|
+
setBusy(true);
|
|
1813
|
+
const finish = (value) => {
|
|
1814
|
+
var _a3, _b2;
|
|
1815
|
+
if (settledRef.current) return;
|
|
1816
|
+
clearWatchdog();
|
|
1817
|
+
const resolved = normalizePermissionStatus(value);
|
|
1818
|
+
setBusy(false);
|
|
1819
|
+
setStatus(resolved);
|
|
1820
|
+
if (resolved === "granted") {
|
|
1821
|
+
playHaptic("success");
|
|
1822
|
+
(_a3 = onStageRef.current) == null ? void 0 : _a3.call(onStageRef, "granted", "granted");
|
|
1823
|
+
} else {
|
|
1824
|
+
(_b2 = onStageRef.current) == null ? void 0 : _b2.call(onStageRef, "denied", resolved);
|
|
1825
|
+
}
|
|
1826
|
+
settle(resolved);
|
|
1827
|
+
};
|
|
1828
|
+
clearWatchdog();
|
|
1829
|
+
watchdogRef.current = setTimeout(() => {
|
|
1830
|
+
watchdogRef.current = null;
|
|
1831
|
+
if (settledRef.current) return;
|
|
1832
|
+
setBusy(false);
|
|
1833
|
+
warnInDev(
|
|
1834
|
+
`[wireai] the \`request\` for the "${permission}" permission screen has not settled after ${Math.round(REQUEST_WATCHDOG_MS / 1e3)}s, so the kit handed the controls back rather than leaving the user on a dead-end screen. It recorded NO outcome, because a pending request is not a denial. Make sure your request resolves to 'granted' | 'denied' | 'blocked' on every branch, including the one where the user dismisses the OS dialog.`
|
|
1835
|
+
);
|
|
1836
|
+
}, REQUEST_WATCHDOG_MS);
|
|
1837
|
+
try {
|
|
1838
|
+
void Promise.resolve(request == null ? void 0 : request()).then(finish, () => finish("denied"));
|
|
1839
|
+
} catch {
|
|
1840
|
+
finish("denied");
|
|
1841
|
+
}
|
|
1842
|
+
}, [busy, primaryAction, status, request, openSettings, settle, clearWatchdog, permission]);
|
|
1843
|
+
const handleSecondary = useCallback(() => {
|
|
1844
|
+
var _a2;
|
|
1845
|
+
if (settledRef.current || busy) return;
|
|
1846
|
+
(_a2 = onStageRef.current) == null ? void 0 : _a2.call(onStageRef, "skipped");
|
|
1847
|
+
settle("skipped");
|
|
1848
|
+
}, [busy, settle]);
|
|
1849
|
+
const popT = useRef(new Animated.Value(reduced ? 1 : 0)).current;
|
|
1850
|
+
const titleT = useRef(new Animated.Value(reduced ? 1 : 0)).current;
|
|
1851
|
+
const bodyT = useRef(new Animated.Value(reduced ? 1 : 0)).current;
|
|
1852
|
+
useEffect(() => {
|
|
1853
|
+
if (reduced) {
|
|
1854
|
+
popT.setValue(1);
|
|
1855
|
+
titleT.setValue(1);
|
|
1856
|
+
bodyT.setValue(1);
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
const rise = (value, delay) => Animated.timing(value, {
|
|
1860
|
+
toValue: 1,
|
|
1861
|
+
duration: scaledMs(INTERSTITIAL_HEAD_MS),
|
|
1862
|
+
delay,
|
|
1863
|
+
easing: easeWire4,
|
|
1864
|
+
useNativeDriver: true
|
|
1865
|
+
});
|
|
1866
|
+
const anims = [
|
|
1867
|
+
Animated.spring(popT, {
|
|
1868
|
+
toValue: 1,
|
|
1869
|
+
friction: STATUS_POP_SPRING.friction,
|
|
1870
|
+
tension: STATUS_POP_SPRING.tension,
|
|
1871
|
+
useNativeDriver: true
|
|
1872
|
+
}),
|
|
1873
|
+
rise(titleT, scaledMs(INTERSTITIAL_HEAD_STAGGER_MS)),
|
|
1874
|
+
rise(bodyT, scaledMs(INTERSTITIAL_HEAD_STAGGER_MS * 2))
|
|
1875
|
+
];
|
|
1876
|
+
anims.forEach((a) => a.start());
|
|
1877
|
+
return () => anims.forEach((a) => a.stop());
|
|
1878
|
+
}, [reduced, popT, titleT, bodyT]);
|
|
1879
|
+
const popScale = popT.interpolate({
|
|
1880
|
+
inputRange: [0, 1],
|
|
1881
|
+
outputRange: [STATUS_POP_SCALE_FROM, 1]
|
|
1882
|
+
});
|
|
1883
|
+
const riseStyle = (value) => ({
|
|
1884
|
+
opacity: value,
|
|
1885
|
+
transform: [
|
|
1886
|
+
{ translateY: value.interpolate({ inputRange: [0, 1], outputRange: [RISE_PX, 0] }) }
|
|
1887
|
+
]
|
|
1888
|
+
});
|
|
1889
|
+
const blocked = status === "blocked";
|
|
1890
|
+
const shownTitle = blocked ? blockedTitle != null ? blockedTitle : title : title;
|
|
1891
|
+
const shownMessage = blocked ? blockedMessage != null ? blockedMessage : message : message;
|
|
1892
|
+
const primaryTitle = primaryAction === "settings" ? settingsLabel != null ? settingsLabel : primaryLabel : primaryAction === "continue" ? continueLabel != null ? continueLabel : primaryLabel : primaryLabel;
|
|
1893
|
+
const showSecondary = status !== "granted";
|
|
1894
|
+
return /* @__PURE__ */ jsx(
|
|
1895
|
+
CardLayout,
|
|
1896
|
+
{
|
|
1897
|
+
align: "center",
|
|
1898
|
+
footer: /* @__PURE__ */ jsxs(View, { style: [styles10.footer, { gap: t.spacing.sm }], children: [
|
|
1899
|
+
/* @__PURE__ */ jsx(Button, { title: primaryTitle, onPress: handlePrimary, variant: "primary", disabled: busy }),
|
|
1900
|
+
showSecondary ? /* @__PURE__ */ jsx(
|
|
1901
|
+
Button,
|
|
1902
|
+
{
|
|
1903
|
+
title: secondaryLabel,
|
|
1904
|
+
onPress: handleSecondary,
|
|
1905
|
+
variant: "outline",
|
|
1906
|
+
disabled: busy
|
|
1907
|
+
}
|
|
1908
|
+
) : null
|
|
1909
|
+
] }),
|
|
1910
|
+
children: /* @__PURE__ */ jsxs(View, { style: [styles10.center, { gap: t.spacing.md }], children: [
|
|
1911
|
+
art ? /* @__PURE__ */ jsx(
|
|
1912
|
+
Animated.View,
|
|
1913
|
+
{
|
|
1914
|
+
style: [styles10.art, { opacity: popT, transform: [{ scale: popScale }] }],
|
|
1915
|
+
children: art
|
|
1916
|
+
}
|
|
1917
|
+
) : null,
|
|
1918
|
+
/* @__PURE__ */ jsx(Animated.View, { style: riseStyle(titleT), children: /* @__PURE__ */ jsx(Text, { style: [headingStyle(t.fonts), { color: t.colors.text, textAlign: "center" }], children: shownTitle }) }),
|
|
1919
|
+
/* @__PURE__ */ jsx(Animated.View, { style: riseStyle(bodyT), children: /* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: shownMessage }) })
|
|
1920
|
+
] })
|
|
1921
|
+
}
|
|
1922
|
+
);
|
|
1923
|
+
};
|
|
1924
|
+
var PermissionCardView = React19.memo(_PermissionCard);
|
|
1925
|
+
var PermissionCard = {
|
|
1926
|
+
name: PERMISSION_CARD_NAME,
|
|
1927
|
+
description: "A priming screen shown BEFORE an OS permission dialog: it explains why the app wants the permission and asks only on the primary tap. Never first, never last. The user is never blocked by it: the secondary advances the flow with the OS prompt unspent.",
|
|
1928
|
+
component: PermissionCardView,
|
|
1929
|
+
propsSchema: schema
|
|
1930
|
+
};
|
|
1931
|
+
var styles10 = StyleSheet.create({
|
|
1932
|
+
center: { width: "100%", alignItems: "center", justifyContent: "center" },
|
|
1933
|
+
art: { alignItems: "center", justifyContent: "center" },
|
|
1934
|
+
footer: { width: "100%", alignItems: "center" }
|
|
1935
|
+
});
|
|
1936
|
+
|
|
1937
|
+
// src/permissions/placement.ts
|
|
1938
|
+
var DEFAULT_PERMISSION_PLACEMENT = "beforeEnd";
|
|
1939
|
+
var normalizeAfterCard = (value) => Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
|
|
1940
|
+
var targetIndex = (placement) => {
|
|
1941
|
+
if (placement === "beforeEnd") return void 0;
|
|
1942
|
+
if (placement === "start") return 0;
|
|
1943
|
+
return normalizeAfterCard(placement.afterCard);
|
|
1944
|
+
};
|
|
1945
|
+
var isPermissionDue = (placement, position) => {
|
|
1946
|
+
if (position.cardIndex < 0) return false;
|
|
1947
|
+
const target = targetIndex(placement);
|
|
1948
|
+
if (target === void 0) return position.isTerminal;
|
|
1949
|
+
return position.cardIndex >= target || position.isTerminal;
|
|
1950
|
+
};
|
|
1951
|
+
var permissionScreenId = (screen, index) => {
|
|
1952
|
+
var _a2;
|
|
1953
|
+
return (_a2 = screen.id) != null ? _a2 : `${screen.permission}:${index}`;
|
|
1954
|
+
};
|
|
1955
|
+
var selectDuePermissionScreen = (screens, settled, position) => {
|
|
1956
|
+
var _a2;
|
|
1957
|
+
if (!screens || screens.length === 0) return void 0;
|
|
1958
|
+
for (let index = 0; index < screens.length; index++) {
|
|
1959
|
+
const screen = screens[index];
|
|
1960
|
+
if (!screen) continue;
|
|
1961
|
+
const id = permissionScreenId(screen, index);
|
|
1962
|
+
if (settled.includes(id)) continue;
|
|
1963
|
+
if (isPermissionDue((_a2 = screen.placement) != null ? _a2 : DEFAULT_PERMISSION_PLACEMENT, position)) {
|
|
1964
|
+
return { screen, id };
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
return void 0;
|
|
1968
|
+
};
|
|
1969
|
+
|
|
1970
|
+
// src/permissions/permissionCopy.ts
|
|
1971
|
+
var NOTIFICATIONS_PERMISSION_COPY = {
|
|
1972
|
+
title: "Want a nudge at the right moment?",
|
|
1973
|
+
message: "Turn notifications on and we will remind you when it actually helps. No daily noise, and you can turn them off any time.",
|
|
1974
|
+
primaryLabel: "Enable notifications",
|
|
1975
|
+
secondaryLabel: "Maybe later",
|
|
1976
|
+
blockedTitle: "Notifications are switched off",
|
|
1977
|
+
blockedMessage: "Notifications are turned off for this app, so we cannot ask from here. You can switch them back on in Settings.",
|
|
1978
|
+
settingsLabel: "Open settings",
|
|
1979
|
+
continueLabel: "Continue"
|
|
1980
|
+
};
|
|
1981
|
+
var GENERIC_PERMISSION_COPY = {
|
|
1982
|
+
title: "One quick permission",
|
|
1983
|
+
message: "We need your permission for this part to work. You can change it any time.",
|
|
1984
|
+
primaryLabel: "Allow",
|
|
1985
|
+
secondaryLabel: "Maybe later",
|
|
1986
|
+
blockedTitle: "Permission is switched off",
|
|
1987
|
+
blockedMessage: "This permission is turned off for this app, so we cannot ask from here. You can switch it back on in Settings.",
|
|
1988
|
+
settingsLabel: "Open settings",
|
|
1989
|
+
continueLabel: "Continue"
|
|
1990
|
+
};
|
|
1991
|
+
var DEFAULT_PERMISSION_COPY = {
|
|
1992
|
+
notifications: NOTIFICATIONS_PERMISSION_COPY
|
|
1993
|
+
};
|
|
1994
|
+
var resolvePermissionCopy = (permission, overrides) => {
|
|
1995
|
+
var _a2;
|
|
1996
|
+
const base = (_a2 = DEFAULT_PERMISSION_COPY[permission]) != null ? _a2 : GENERIC_PERMISSION_COPY;
|
|
1997
|
+
if (!overrides) return base;
|
|
1998
|
+
const merged = { ...base };
|
|
1999
|
+
for (const key of Object.keys(overrides)) {
|
|
2000
|
+
const value = overrides[key];
|
|
2001
|
+
if (typeof value === "string" && value.length > 0) merged[key] = value;
|
|
2002
|
+
}
|
|
2003
|
+
return merged;
|
|
2004
|
+
};
|
|
1579
2005
|
var SKIP_ONE_SENTINEL = "__wireai_skip__";
|
|
1580
2006
|
var ANSWER_CALLBACKS = ["onSubmit", "onSelect", "onConfirm", "onPress"];
|
|
1581
2007
|
var DEFAULT_COPY = {
|
|
@@ -1604,7 +2030,11 @@ var OnboardingFlow = ({
|
|
|
1604
2030
|
reportTarget,
|
|
1605
2031
|
sessionId,
|
|
1606
2032
|
resumed = false,
|
|
1607
|
-
clientContext
|
|
2033
|
+
clientContext,
|
|
2034
|
+
permissionScreens,
|
|
2035
|
+
settledPermissions,
|
|
2036
|
+
permissionsPending = false,
|
|
2037
|
+
onPermissionSettled
|
|
1608
2038
|
}) => {
|
|
1609
2039
|
var _a2, _b, _c, _d, _e, _f;
|
|
1610
2040
|
const { messages, error, isLoading, sendMessage, reset } = useWireAIThread();
|
|
@@ -1635,6 +2065,9 @@ var OnboardingFlow = ({
|
|
|
1635
2065
|
const [timedOut, setTimedOut] = useState(false);
|
|
1636
2066
|
const [validationError, setValidationError] = useState();
|
|
1637
2067
|
const [validating, setValidating] = useState(false);
|
|
2068
|
+
const [locallySettledPermissions, setLocallySettledPermissions] = useState([]);
|
|
2069
|
+
const onPermissionSettledRef = useRef(onPermissionSettled);
|
|
2070
|
+
onPermissionSettledRef.current = onPermissionSettled;
|
|
1638
2071
|
const lastCard = useMemo(
|
|
1639
2072
|
() => [...messages].reverse().find((m) => {
|
|
1640
2073
|
var _a3;
|
|
@@ -1745,6 +2178,36 @@ var OnboardingFlow = ({
|
|
|
1745
2178
|
if (isLoading) return;
|
|
1746
2179
|
sendMessage(SKIP_ONE_SENTINEL);
|
|
1747
2180
|
}, [isLoading, sendMessage]);
|
|
2181
|
+
const emitPermissionStage = useCallback(
|
|
2182
|
+
(permission, stage, status) => {
|
|
2183
|
+
var _a3, _b2, _c2, _d2;
|
|
2184
|
+
(_a3 = onEventRef.current) == null ? void 0 : _a3.call(onEventRef, { type: "permission", permission, stage, status });
|
|
2185
|
+
reportClientEvent(reportTargetRef.current, {
|
|
2186
|
+
event_type: "app_event",
|
|
2187
|
+
session_id: sessionIdRef.current,
|
|
2188
|
+
question_key: permissionEventName(stage),
|
|
2189
|
+
component: PERMISSION_CARD_NAME,
|
|
2190
|
+
screen_index: lastScreenIndexRef.current >= 0 ? lastScreenIndexRef.current : void 0,
|
|
2191
|
+
meta: JSON.stringify(permissionEventProps(permission, status)),
|
|
2192
|
+
device: (_b2 = clientContextRef.current) == null ? void 0 : _b2.device,
|
|
2193
|
+
user_context: (_c2 = clientContextRef.current) == null ? void 0 : _c2.userContext,
|
|
2194
|
+
user_id: (_d2 = clientContextRef.current) == null ? void 0 : _d2.userId
|
|
2195
|
+
});
|
|
2196
|
+
},
|
|
2197
|
+
[]
|
|
2198
|
+
);
|
|
2199
|
+
const settlePermission = useCallback(
|
|
2200
|
+
(id, screen, outcome) => {
|
|
2201
|
+
var _a3, _b2;
|
|
2202
|
+
setLocallySettledPermissions((prev) => prev.includes(id) ? prev : [...prev, id]);
|
|
2203
|
+
(_a3 = onPermissionSettledRef.current) == null ? void 0 : _a3.call(onPermissionSettledRef, id, outcome);
|
|
2204
|
+
try {
|
|
2205
|
+
(_b2 = screen.onResult) == null ? void 0 : _b2.call(screen, screen.permission, outcome);
|
|
2206
|
+
} catch {
|
|
2207
|
+
}
|
|
2208
|
+
},
|
|
2209
|
+
[]
|
|
2210
|
+
);
|
|
1748
2211
|
const previewOnSelect = useCallback((value) => {
|
|
1749
2212
|
const target = reportTargetRef.current;
|
|
1750
2213
|
if (!(target == null ? void 0 : target.serverUrl)) return;
|
|
@@ -1764,8 +2227,8 @@ var OnboardingFlow = ({
|
|
|
1764
2227
|
(messageId) => {
|
|
1765
2228
|
var _a3;
|
|
1766
2229
|
const base = makeActions(messageId);
|
|
1767
|
-
const
|
|
1768
|
-
const validator =
|
|
2230
|
+
const validators2 = validatorsRef.current;
|
|
2231
|
+
const validator = (_a3 = progress.slot_id ? validators2 == null ? void 0 : validators2[progress.slot_id] : void 0) != null ? _a3 : progress.key ? validators2 == null ? void 0 : validators2[progress.key] : void 0;
|
|
1769
2232
|
if (!validator) return base;
|
|
1770
2233
|
const out = { ...base };
|
|
1771
2234
|
for (const name2 of ANSWER_CALLBACKS) {
|
|
@@ -1790,7 +2253,7 @@ var OnboardingFlow = ({
|
|
|
1790
2253
|
}
|
|
1791
2254
|
return out;
|
|
1792
2255
|
},
|
|
1793
|
-
[makeActions, progress.key]
|
|
2256
|
+
[makeActions, progress.key, progress.slot_id]
|
|
1794
2257
|
);
|
|
1795
2258
|
if (degraded) {
|
|
1796
2259
|
if (fallbackFlow !== void 0) return /* @__PURE__ */ jsx(Fragment, { children: fallbackFlow });
|
|
@@ -1815,6 +2278,36 @@ var OnboardingFlow = ({
|
|
|
1815
2278
|
const step = (_a2 = progress.step) != null ? _a2 : renderedCount;
|
|
1816
2279
|
const total = (_b = progress.total) != null ? _b : approxScreens;
|
|
1817
2280
|
const isTerminal = response.action === "render" && response.component === "StatusCard";
|
|
2281
|
+
const duePermission = permissionsPending ? void 0 : selectDuePermissionScreen(
|
|
2282
|
+
permissionScreens,
|
|
2283
|
+
settledPermissions ? [...settledPermissions, ...locallySettledPermissions] : locallySettledPermissions,
|
|
2284
|
+
{ cardIndex: renderedCount - 1, isTerminal }
|
|
2285
|
+
);
|
|
2286
|
+
if (duePermission) {
|
|
2287
|
+
const { screen, id } = duePermission;
|
|
2288
|
+
const copy2 = resolvePermissionCopy(screen.permission, screen.copy);
|
|
2289
|
+
return /* @__PURE__ */ jsx(OnboardingScaffold, { step, approxScreens: total, children: /* @__PURE__ */ jsx(CardHandoff, { transitionKey: `permission:${id}`, variant: "spring", children: /* @__PURE__ */ jsx(
|
|
2290
|
+
PermissionCardView,
|
|
2291
|
+
{
|
|
2292
|
+
permission: screen.permission,
|
|
2293
|
+
title: copy2.title,
|
|
2294
|
+
message: copy2.message,
|
|
2295
|
+
primaryLabel: copy2.primaryLabel,
|
|
2296
|
+
secondaryLabel: copy2.secondaryLabel,
|
|
2297
|
+
blockedTitle: copy2.blockedTitle,
|
|
2298
|
+
blockedMessage: copy2.blockedMessage,
|
|
2299
|
+
settingsLabel: copy2.settingsLabel,
|
|
2300
|
+
continueLabel: copy2.continueLabel,
|
|
2301
|
+
illustration: screen.illustration,
|
|
2302
|
+
request: screen.request,
|
|
2303
|
+
getStatus: screen.getStatus,
|
|
2304
|
+
openSettings: screen.openSettings,
|
|
2305
|
+
onStage: (stage, status) => emitPermissionStage(screen.permission, stage, status),
|
|
2306
|
+
onSettled: (outcome) => settlePermission(id, screen, outcome)
|
|
2307
|
+
},
|
|
2308
|
+
id
|
|
2309
|
+
) }) });
|
|
2310
|
+
}
|
|
1818
2311
|
if (isTerminal) {
|
|
1819
2312
|
const props = (_c = response.props) != null ? _c : {};
|
|
1820
2313
|
return /* @__PURE__ */ jsx(OnboardingScaffold, { step, complete: true, approxScreens: total, children: /* @__PURE__ */ jsx(
|
|
@@ -1873,8 +2366,8 @@ var OnboardingFlow = ({
|
|
|
1873
2366
|
}
|
|
1874
2367
|
);
|
|
1875
2368
|
};
|
|
1876
|
-
var
|
|
1877
|
-
var
|
|
2369
|
+
var easeWire5 = Easing.bezier(...WIRE_BEZIER);
|
|
2370
|
+
var schema2 = z.object({
|
|
1878
2371
|
title: z.string().describe("Question or section header, e.g. 'Which season?'"),
|
|
1879
2372
|
chips: z.array(z.string()).min(1).describe("List of option labels shown as chips, e.g. ['Spring','Summer','Autumn','Winter']"),
|
|
1880
2373
|
multiSelect: z.boolean().optional().describe("Allow multiple chips selected at once, default true"),
|
|
@@ -1905,7 +2398,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
|
|
|
1905
2398
|
const anim = Animated.timing(fillT, {
|
|
1906
2399
|
toValue: isSelected ? 1 : 0,
|
|
1907
2400
|
duration: scaledMs(stateChangeDuration(reduced, CHIP_FILL_MS)),
|
|
1908
|
-
easing:
|
|
2401
|
+
easing: easeWire5,
|
|
1909
2402
|
useNativeDriver: true
|
|
1910
2403
|
});
|
|
1911
2404
|
anim.start();
|
|
@@ -1945,7 +2438,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
|
|
|
1945
2438
|
Animated.View,
|
|
1946
2439
|
{
|
|
1947
2440
|
style: [
|
|
1948
|
-
|
|
2441
|
+
styles11.chip,
|
|
1949
2442
|
{
|
|
1950
2443
|
borderRadius: t.radius.full,
|
|
1951
2444
|
paddingHorizontal: t.spacing.md,
|
|
@@ -1962,7 +2455,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
|
|
|
1962
2455
|
pointerEvents: "none",
|
|
1963
2456
|
style: [
|
|
1964
2457
|
StyleSheet.absoluteFill,
|
|
1965
|
-
|
|
2458
|
+
styles11.fillOverlay,
|
|
1966
2459
|
{
|
|
1967
2460
|
borderRadius: t.radius.full,
|
|
1968
2461
|
borderColor: t.colors.primary,
|
|
@@ -1976,7 +2469,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
|
|
|
1976
2469
|
Animated.Text,
|
|
1977
2470
|
{
|
|
1978
2471
|
style: [
|
|
1979
|
-
|
|
2472
|
+
styles11.check,
|
|
1980
2473
|
{ color: t.colors.onPrimary, opacity: checkT, transform: [{ scale: checkScale }] }
|
|
1981
2474
|
],
|
|
1982
2475
|
children: "\u2713"
|
|
@@ -1987,7 +2480,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
|
|
|
1987
2480
|
{
|
|
1988
2481
|
style: [
|
|
1989
2482
|
captionStyle(t.fonts),
|
|
1990
|
-
|
|
2483
|
+
styles11.chipLabel,
|
|
1991
2484
|
{ color: isSelected ? t.colors.onPrimary : t.colors.textMuted }
|
|
1992
2485
|
],
|
|
1993
2486
|
children: chip
|
|
@@ -1999,7 +2492,7 @@ var _ChipItem = ({ chip, isSelected, multiSelect, onToggle }) => {
|
|
|
1999
2492
|
}
|
|
2000
2493
|
);
|
|
2001
2494
|
};
|
|
2002
|
-
var ChipItem =
|
|
2495
|
+
var ChipItem = React19.memo(_ChipItem);
|
|
2003
2496
|
var _ChipSelectCard = ({
|
|
2004
2497
|
title,
|
|
2005
2498
|
chips,
|
|
@@ -2049,7 +2542,7 @@ var _ChipSelectCard = ({
|
|
|
2049
2542
|
disabled: selected.length === 0 || submitted
|
|
2050
2543
|
}
|
|
2051
2544
|
),
|
|
2052
|
-
children: /* @__PURE__ */ jsx(View, { style: [
|
|
2545
|
+
children: /* @__PURE__ */ jsx(View, { style: [styles11.chipsRow, { gap: t.spacing.sm + t.spacing.xs }], children: chips.map((chip) => /* @__PURE__ */ jsx(
|
|
2053
2546
|
ChipItem,
|
|
2054
2547
|
{
|
|
2055
2548
|
chip,
|
|
@@ -2065,18 +2558,18 @@ var _ChipSelectCard = ({
|
|
|
2065
2558
|
var ChipSelectCard = {
|
|
2066
2559
|
name: "ChipSelectCard",
|
|
2067
2560
|
description: "Use for compact short labels: seasons, activities, travel styles, moods, categories. Ideal for 4\u201312 single-word or two-word options. Use SelectionCard instead when options need descriptions; use CardGridSelectCard for a smaller set (2\u20136) of visual choices that each deserve an icon.",
|
|
2068
|
-
component:
|
|
2069
|
-
propsSchema:
|
|
2561
|
+
component: React19.memo(_ChipSelectCard),
|
|
2562
|
+
propsSchema: schema2,
|
|
2070
2563
|
defaultProps: { multiSelect: true, submitLabel: "Continue" }
|
|
2071
2564
|
};
|
|
2072
|
-
var
|
|
2565
|
+
var styles11 = StyleSheet.create({
|
|
2073
2566
|
chipsRow: { flexDirection: "row", flexWrap: "wrap" },
|
|
2074
2567
|
chip: { borderWidth: 1.5, flexDirection: "row", alignItems: "center", gap: 6, overflow: "hidden" },
|
|
2075
2568
|
fillOverlay: { borderWidth: 1.5 },
|
|
2076
2569
|
chipLabel: { fontWeight: "600" },
|
|
2077
2570
|
check: { fontSize: 13, fontWeight: "800", lineHeight: 15 }
|
|
2078
2571
|
});
|
|
2079
|
-
var
|
|
2572
|
+
var schema3 = z.object({
|
|
2080
2573
|
label: z.string().describe("Input field label / the question being asked"),
|
|
2081
2574
|
hint: z.string().optional().describe(
|
|
2082
2575
|
"One short line under the question giving context: what the answer is used for or why you ask. Add it whenever the question alone might be unclear."
|
|
@@ -2162,8 +2655,8 @@ var _TextInputCard = ({
|
|
|
2162
2655
|
var TextInputCard = {
|
|
2163
2656
|
name: "TextInputCard",
|
|
2164
2657
|
description: "Use when the user needs to type a free-text answer: an open-ended goal, notes, feedback, a name, a destination, a search query. Multi-line by default (great for open-ended goals); set multiline:false for a short single-line field like a name. Use NumberStepperCard for numeric ranges with known bounds.",
|
|
2165
|
-
component:
|
|
2166
|
-
propsSchema:
|
|
2658
|
+
component: React19.memo(_TextInputCard),
|
|
2659
|
+
propsSchema: schema3,
|
|
2167
2660
|
defaultProps: { submitLabel: "Submit", multiline: true, lines: 6 }
|
|
2168
2661
|
};
|
|
2169
2662
|
function makeStyles3(t) {
|
|
@@ -2318,7 +2811,7 @@ var _WireIcon = ({ name: name2, size = 20, color, requireModule }) => {
|
|
|
2318
2811
|
return /* @__PURE__ */ jsx(
|
|
2319
2812
|
View,
|
|
2320
2813
|
{
|
|
2321
|
-
style:
|
|
2814
|
+
style: styles12.decorative,
|
|
2322
2815
|
accessible: false,
|
|
2323
2816
|
importantForAccessibility: "no-hide-descendants",
|
|
2324
2817
|
children: hostIcon
|
|
@@ -2332,7 +2825,7 @@ var _WireIcon = ({ name: name2, size = 20, color, requireModule }) => {
|
|
|
2332
2825
|
return /* @__PURE__ */ jsx(
|
|
2333
2826
|
View,
|
|
2334
2827
|
{
|
|
2335
|
-
style:
|
|
2828
|
+
style: styles12.decorative,
|
|
2336
2829
|
accessible: false,
|
|
2337
2830
|
importantForAccessibility: "no-hide-descendants",
|
|
2338
2831
|
children: /* @__PURE__ */ jsx(
|
|
@@ -2347,9 +2840,9 @@ var _WireIcon = ({ name: name2, size = 20, color, requireModule }) => {
|
|
|
2347
2840
|
}
|
|
2348
2841
|
);
|
|
2349
2842
|
};
|
|
2350
|
-
var WireIcon =
|
|
2843
|
+
var WireIcon = React19.memo(_WireIcon);
|
|
2351
2844
|
WireIcon.displayName = "WireIcon";
|
|
2352
|
-
var
|
|
2845
|
+
var styles12 = StyleSheet.create({
|
|
2353
2846
|
// No size/margin of its own: the glyph decides its footprint, so an absent icon and a
|
|
2354
2847
|
// present one differ by exactly the glyph — never by a wrapper.
|
|
2355
2848
|
decorative: {}
|
|
@@ -2363,8 +2856,8 @@ var optionObjectSchema = z.object({
|
|
|
2363
2856
|
var coerceOptions = (arr) => Array.isArray(arr) ? arr.map((item) => typeof item === "string" ? { value: item, label: item } : item) : arr;
|
|
2364
2857
|
var optionsField = (description) => z.preprocess(coerceOptions, z.array(optionObjectSchema).min(1)).describe(description);
|
|
2365
2858
|
var normalizeOptions = (options) => options.map((opt) => typeof opt === "string" ? { value: opt, label: opt } : opt);
|
|
2366
|
-
var
|
|
2367
|
-
var
|
|
2859
|
+
var easeWire6 = Easing.bezier(...WIRE_BEZIER);
|
|
2860
|
+
var schema4 = z.object({
|
|
2368
2861
|
title: z.string().describe("Question or prompt for the user"),
|
|
2369
2862
|
options: optionsField("List of choices"),
|
|
2370
2863
|
submitLabel: z.string().optional().describe("Submit button label, default: Continue"),
|
|
@@ -2394,7 +2887,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
|
|
|
2394
2887
|
const anim = Animated.timing(selectT, {
|
|
2395
2888
|
toValue: isSelected ? 1 : 0,
|
|
2396
2889
|
duration: scaledMs(stateChangeDuration(reduced, SELECT_FILL_MS)),
|
|
2397
|
-
easing:
|
|
2890
|
+
easing: easeWire6,
|
|
2398
2891
|
useNativeDriver: true
|
|
2399
2892
|
});
|
|
2400
2893
|
anim.start();
|
|
@@ -2434,7 +2927,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
|
|
|
2434
2927
|
Animated.View,
|
|
2435
2928
|
{
|
|
2436
2929
|
style: [
|
|
2437
|
-
|
|
2930
|
+
styles13.option,
|
|
2438
2931
|
{
|
|
2439
2932
|
padding: t.spacing.md,
|
|
2440
2933
|
borderRadius: t.radius.md,
|
|
@@ -2451,7 +2944,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
|
|
|
2451
2944
|
pointerEvents: "none",
|
|
2452
2945
|
style: [
|
|
2453
2946
|
StyleSheet.absoluteFill,
|
|
2454
|
-
|
|
2947
|
+
styles13.selectedOverlay,
|
|
2455
2948
|
{
|
|
2456
2949
|
borderRadius: t.radius.md,
|
|
2457
2950
|
borderColor: t.colors.primary,
|
|
@@ -2465,7 +2958,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
|
|
|
2465
2958
|
View,
|
|
2466
2959
|
{
|
|
2467
2960
|
style: [
|
|
2468
|
-
|
|
2961
|
+
styles13.indicator,
|
|
2469
2962
|
{ borderRadius: t.radius.full },
|
|
2470
2963
|
isSelected ? { borderColor: t.colors.primary, backgroundColor: t.colors.primary } : { borderColor: t.colors.borderStrong, backgroundColor: "transparent" }
|
|
2471
2964
|
],
|
|
@@ -2473,7 +2966,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
|
|
|
2473
2966
|
Animated.Text,
|
|
2474
2967
|
{
|
|
2475
2968
|
style: [
|
|
2476
|
-
|
|
2969
|
+
styles13.check,
|
|
2477
2970
|
{ color: t.colors.onPrimary, opacity: checkT, transform: [{ scale: checkScale }] }
|
|
2478
2971
|
],
|
|
2479
2972
|
children: "\u2713"
|
|
@@ -2489,7 +2982,7 @@ var _OptionRow = ({ opt, isSelected, multiSelect, onToggle }) => {
|
|
|
2489
2982
|
}
|
|
2490
2983
|
);
|
|
2491
2984
|
};
|
|
2492
|
-
var OptionRow =
|
|
2985
|
+
var OptionRow = React19.memo(_OptionRow);
|
|
2493
2986
|
var _SelectionCard = ({
|
|
2494
2987
|
title,
|
|
2495
2988
|
options,
|
|
@@ -2499,7 +2992,7 @@ var _SelectionCard = ({
|
|
|
2499
2992
|
onSelectionChange
|
|
2500
2993
|
}) => {
|
|
2501
2994
|
const t = useOnboardingTheme();
|
|
2502
|
-
const opts =
|
|
2995
|
+
const opts = React19.useMemo(() => normalizeOptions(options), [options]);
|
|
2503
2996
|
const [selected, setSelected] = useState([]);
|
|
2504
2997
|
const [submitted, setSubmitted] = useState(false);
|
|
2505
2998
|
useEffect(() => {
|
|
@@ -2550,18 +3043,18 @@ var _SelectionCard = ({
|
|
|
2550
3043
|
var SelectionCard = {
|
|
2551
3044
|
name: "SelectionCard",
|
|
2552
3045
|
description: "Use for mutually exclusive (radio) or multi-select (checkbox) choices from 2\u20138 options, as a vertical list of rows. Best when options are wordy or need reading. Options may carry an optional `icon` from the vocabulary to make the list scannable. Use ChipSelectCard instead when options are short single words; use CardGridSelectCard when the options are visual and each has an obvious icon (occasions, channels, categories).",
|
|
2553
|
-
component:
|
|
2554
|
-
propsSchema:
|
|
3046
|
+
component: React19.memo(_SelectionCard),
|
|
3047
|
+
propsSchema: schema4,
|
|
2555
3048
|
defaultProps: { submitLabel: "Continue", multiSelect: false }
|
|
2556
3049
|
};
|
|
2557
|
-
var
|
|
3050
|
+
var styles13 = StyleSheet.create({
|
|
2558
3051
|
option: { borderWidth: 1.5, flexDirection: "row", alignItems: "center", overflow: "hidden" },
|
|
2559
3052
|
selectedOverlay: { borderWidth: 1.5 },
|
|
2560
3053
|
indicator: { width: 24, height: 24, borderWidth: 2, alignItems: "center", justifyContent: "center" },
|
|
2561
3054
|
check: { fontSize: 13, fontWeight: "800", lineHeight: 15 }
|
|
2562
3055
|
});
|
|
2563
|
-
var
|
|
2564
|
-
var
|
|
3056
|
+
var easeWire7 = Easing.bezier(...WIRE_BEZIER);
|
|
3057
|
+
var schema5 = z.object({
|
|
2565
3058
|
title: z.string().describe("Question or prompt for the user, e.g. 'What brings you here?'"),
|
|
2566
3059
|
options: optionsField(
|
|
2567
3060
|
"The choices, 2-6 of them. Give EVERY option an `icon` from the vocabulary \u2014 the icon is the point of this card, and an option without one renders as a bare label next to icon-bearing neighbours. Keep labels short (1-3 words) so they fit a half-width card."
|
|
@@ -2593,7 +3086,7 @@ var _OptionCard = ({ opt, isSelected, multiSelect, onToggle }) => {
|
|
|
2593
3086
|
const anim = Animated.timing(selectT, {
|
|
2594
3087
|
toValue: isSelected ? 1 : 0,
|
|
2595
3088
|
duration: scaledMs(stateChangeDuration(reduced, SELECT_FILL_MS)),
|
|
2596
|
-
easing:
|
|
3089
|
+
easing: easeWire7,
|
|
2597
3090
|
useNativeDriver: true
|
|
2598
3091
|
});
|
|
2599
3092
|
anim.start();
|
|
@@ -2608,12 +3101,12 @@ var _OptionCard = ({ opt, isSelected, multiSelect, onToggle }) => {
|
|
|
2608
3101
|
accessibilityRole: multiSelect ? "checkbox" : "radio",
|
|
2609
3102
|
accessibilityState: multiSelect ? { checked: isSelected } : { selected: isSelected },
|
|
2610
3103
|
accessibilityLabel: opt.label,
|
|
2611
|
-
style:
|
|
3104
|
+
style: styles14.cell,
|
|
2612
3105
|
children: /* @__PURE__ */ jsxs(
|
|
2613
3106
|
Animated.View,
|
|
2614
3107
|
{
|
|
2615
3108
|
style: [
|
|
2616
|
-
|
|
3109
|
+
styles14.card,
|
|
2617
3110
|
{
|
|
2618
3111
|
padding: t.spacing.md,
|
|
2619
3112
|
borderRadius: t.radius.lg,
|
|
@@ -2630,7 +3123,7 @@ var _OptionCard = ({ opt, isSelected, multiSelect, onToggle }) => {
|
|
|
2630
3123
|
pointerEvents: "none",
|
|
2631
3124
|
style: [
|
|
2632
3125
|
StyleSheet.absoluteFill,
|
|
2633
|
-
|
|
3126
|
+
styles14.selectedOverlay,
|
|
2634
3127
|
{
|
|
2635
3128
|
borderRadius: t.radius.lg,
|
|
2636
3129
|
borderColor: t.colors.primary,
|
|
@@ -2648,7 +3141,7 @@ var _OptionCard = ({ opt, isSelected, multiSelect, onToggle }) => {
|
|
|
2648
3141
|
}
|
|
2649
3142
|
);
|
|
2650
3143
|
};
|
|
2651
|
-
var OptionCard =
|
|
3144
|
+
var OptionCard = React19.memo(_OptionCard);
|
|
2652
3145
|
var _CardGridSelectCard = ({
|
|
2653
3146
|
title,
|
|
2654
3147
|
options,
|
|
@@ -2658,7 +3151,7 @@ var _CardGridSelectCard = ({
|
|
|
2658
3151
|
onSelectionChange
|
|
2659
3152
|
}) => {
|
|
2660
3153
|
const t = useOnboardingTheme();
|
|
2661
|
-
const opts =
|
|
3154
|
+
const opts = React19.useMemo(() => normalizeOptions(options), [options]);
|
|
2662
3155
|
const [selected, setSelected] = useState([]);
|
|
2663
3156
|
const [submitted, setSubmitted] = useState(false);
|
|
2664
3157
|
useEffect(() => {
|
|
@@ -2695,7 +3188,7 @@ var _CardGridSelectCard = ({
|
|
|
2695
3188
|
disabled: selected.length === 0 || submitted
|
|
2696
3189
|
}
|
|
2697
3190
|
),
|
|
2698
|
-
children: /* @__PURE__ */ jsx(View, { style: [
|
|
3191
|
+
children: /* @__PURE__ */ jsx(View, { style: [styles14.grid, { gap: t.spacing.sm }], children: opts.map((opt) => /* @__PURE__ */ jsx(
|
|
2699
3192
|
OptionCard,
|
|
2700
3193
|
{
|
|
2701
3194
|
opt,
|
|
@@ -2711,19 +3204,19 @@ var _CardGridSelectCard = ({
|
|
|
2711
3204
|
var CardGridSelectCard = {
|
|
2712
3205
|
name: "CardGridSelectCard",
|
|
2713
3206
|
description: "Use for VISUAL, icon-led choices shown as a 2-column grid of tappable cards (icon on top, short label beneath). Ideal for 2\u20136 concrete options that each have an obvious icon \u2014 occasions, channels, goals, categories \u2014 e.g. 'What brings you here?' or 'Where did you hear about us?'. Multi-select by default. Every option should carry an `icon`. Use SelectionCard instead when options need descriptions or have no natural icon; use ChipSelectCard for many short word-like tags.",
|
|
2714
|
-
component:
|
|
2715
|
-
propsSchema:
|
|
3207
|
+
component: React19.memo(_CardGridSelectCard),
|
|
3208
|
+
propsSchema: schema5,
|
|
2716
3209
|
defaultProps: { submitLabel: "Continue", multiSelect: true }
|
|
2717
3210
|
};
|
|
2718
|
-
var
|
|
3211
|
+
var styles14 = StyleSheet.create({
|
|
2719
3212
|
grid: { flexDirection: "row", flexWrap: "wrap" },
|
|
2720
3213
|
// Two per row: ~47% basis leaves room for the gap, flexGrow fills the remainder exactly.
|
|
2721
3214
|
cell: { flexBasis: "47%", flexGrow: 1 },
|
|
2722
3215
|
card: { borderWidth: 1.5, alignItems: "flex-start", overflow: "hidden" },
|
|
2723
3216
|
selectedOverlay: { borderWidth: 1.5 }
|
|
2724
3217
|
});
|
|
2725
|
-
var
|
|
2726
|
-
var
|
|
3218
|
+
var easeWire8 = Easing.bezier(...WIRE_BEZIER);
|
|
3219
|
+
var schema6 = z.object({
|
|
2727
3220
|
status: z.enum(["success", "warning", "error", "info"]).describe("Status type"),
|
|
2728
3221
|
title: z.string().describe("Status title"),
|
|
2729
3222
|
message: z.string().optional().describe("Supporting message"),
|
|
@@ -2773,7 +3266,7 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
|
|
|
2773
3266
|
toValue: 1,
|
|
2774
3267
|
duration: scaledMs(STATUS_COPY_MS),
|
|
2775
3268
|
delay: scaledMs(STATUS_COPY_STAGGER_MS),
|
|
2776
|
-
easing:
|
|
3269
|
+
easing: easeWire8,
|
|
2777
3270
|
useNativeDriver: true
|
|
2778
3271
|
})
|
|
2779
3272
|
];
|
|
@@ -2795,8 +3288,8 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
|
|
|
2795
3288
|
{
|
|
2796
3289
|
align: "center",
|
|
2797
3290
|
footer: ctaLabel ? /* @__PURE__ */ jsx(Button, { title: ctaLabel, onPress: handleContinue, variant: "primary", disabled: submitted }) : void 0,
|
|
2798
|
-
children: /* @__PURE__ */ jsxs(View, { style: [
|
|
2799
|
-
/* @__PURE__ */ jsxs(View, { style: [
|
|
3291
|
+
children: /* @__PURE__ */ jsxs(View, { style: [styles15.center, { gap: t.spacing.sm }], children: [
|
|
3292
|
+
/* @__PURE__ */ jsxs(View, { style: [styles15.titleRow, { gap: t.spacing.sm }], children: [
|
|
2800
3293
|
/* @__PURE__ */ jsx(
|
|
2801
3294
|
Animated.Text,
|
|
2802
3295
|
{
|
|
@@ -2840,14 +3333,14 @@ var _StatusCard = ({ status, title, message, ctaLabel, onContinue, onPress, onSu
|
|
|
2840
3333
|
var StatusCard = {
|
|
2841
3334
|
name: "StatusCard",
|
|
2842
3335
|
description: "Use ONLY for real outcomes: booking confirmed, data saved, action completed. NEVER for 'you just viewed X' acknowledgments. Always include ctaLabel so the user can continue after seeing the status.",
|
|
2843
|
-
component:
|
|
2844
|
-
propsSchema:
|
|
3336
|
+
component: React19.memo(_StatusCard),
|
|
3337
|
+
propsSchema: schema6
|
|
2845
3338
|
};
|
|
2846
|
-
var
|
|
3339
|
+
var styles15 = StyleSheet.create({
|
|
2847
3340
|
center: { width: "100%", alignItems: "center", justifyContent: "center" },
|
|
2848
3341
|
titleRow: { flexDirection: "row", alignItems: "center", justifyContent: "center" }
|
|
2849
3342
|
});
|
|
2850
|
-
var
|
|
3343
|
+
var schema7 = z.object({
|
|
2851
3344
|
label: z.string().describe("What is being counted, e.g. 'Number of days' or 'Travellers'"),
|
|
2852
3345
|
min: z.number().optional().describe("Minimum value, default 1"),
|
|
2853
3346
|
max: z.number().optional().describe("Maximum value, default 30"),
|
|
@@ -2918,8 +3411,8 @@ var _NumberStepperCard = ({
|
|
|
2918
3411
|
var NumberStepperCard = {
|
|
2919
3412
|
name: "NumberStepperCard",
|
|
2920
3413
|
description: "Use for any bounded numeric input: days, people, portions, budget tiers, ratings. Best for ranges 1\u201399. Set unit (e.g. 'days', 'people') for clarity.",
|
|
2921
|
-
component:
|
|
2922
|
-
propsSchema:
|
|
3414
|
+
component: React19.memo(_NumberStepperCard),
|
|
3415
|
+
propsSchema: schema7,
|
|
2923
3416
|
defaultProps: { min: 1, max: 30, defaultValue: 1, step: 1, submitLabel: "Confirm" }
|
|
2924
3417
|
};
|
|
2925
3418
|
function makeStyles4(t) {
|
|
@@ -2930,9 +3423,9 @@ function makeStyles4(t) {
|
|
|
2930
3423
|
valueBox: { alignItems: "center", minWidth: 80 }
|
|
2931
3424
|
});
|
|
2932
3425
|
}
|
|
2933
|
-
var
|
|
2934
|
-
var
|
|
2935
|
-
var
|
|
3426
|
+
var easeWire9 = Easing.bezier(...WIRE_BEZIER);
|
|
3427
|
+
var RISE_PX2 = 10;
|
|
3428
|
+
var schema8 = z.object({
|
|
2936
3429
|
title: z.string().describe("Short, warm headline for the value/momentum moment"),
|
|
2937
3430
|
body: z.string().optional().describe("One or two supporting lines that reflect the user's answers back and build momentum"),
|
|
2938
3431
|
items: z.array(z.string()).max(4).optional().describe(
|
|
@@ -2969,7 +3462,7 @@ var _WorkItem = ({
|
|
|
2969
3462
|
toValue: 1,
|
|
2970
3463
|
duration: scaledMs(INTERSTITIAL_ITEM_MS),
|
|
2971
3464
|
delay: scaledMs(interstitialItemDelay(index)),
|
|
2972
|
-
easing:
|
|
3465
|
+
easing: easeWire9,
|
|
2973
3466
|
useNativeDriver: true
|
|
2974
3467
|
}),
|
|
2975
3468
|
Animated.spring(checkT, {
|
|
@@ -2983,7 +3476,7 @@ var _WorkItem = ({
|
|
|
2983
3476
|
anims.forEach((a) => a.start());
|
|
2984
3477
|
return () => anims.forEach((a) => a.stop());
|
|
2985
3478
|
}, [reduced, index, rowT, checkT]);
|
|
2986
|
-
const rise = rowT.interpolate({ inputRange: [0, 1], outputRange: [
|
|
3479
|
+
const rise = rowT.interpolate({ inputRange: [0, 1], outputRange: [RISE_PX2, 0] });
|
|
2987
3480
|
const checkScale = checkT.interpolate({
|
|
2988
3481
|
inputRange: [0, 1],
|
|
2989
3482
|
outputRange: [INTERSTITIAL_CHECK_SCALE_FROM, 1]
|
|
@@ -2991,20 +3484,20 @@ var _WorkItem = ({
|
|
|
2991
3484
|
return /* @__PURE__ */ jsxs(
|
|
2992
3485
|
Animated.View,
|
|
2993
3486
|
{
|
|
2994
|
-
style: [
|
|
3487
|
+
style: [styles16.itemRow, { gap: t.spacing.sm + t.spacing.xs, opacity: rowT, transform: [{ translateY: rise }] }],
|
|
2995
3488
|
children: [
|
|
2996
3489
|
/* @__PURE__ */ jsx(
|
|
2997
3490
|
View,
|
|
2998
3491
|
{
|
|
2999
3492
|
style: [
|
|
3000
|
-
|
|
3493
|
+
styles16.itemBadge,
|
|
3001
3494
|
{ backgroundColor: t.colors.primarySoft, borderRadius: t.radius.full }
|
|
3002
3495
|
],
|
|
3003
3496
|
children: /* @__PURE__ */ jsx(
|
|
3004
3497
|
Animated.Text,
|
|
3005
3498
|
{
|
|
3006
3499
|
style: [
|
|
3007
|
-
|
|
3500
|
+
styles16.itemCheck,
|
|
3008
3501
|
{ color: t.colors.primary, opacity: checkT, transform: [{ scale: checkScale }] }
|
|
3009
3502
|
],
|
|
3010
3503
|
children: "\u2713"
|
|
@@ -3012,12 +3505,12 @@ var _WorkItem = ({
|
|
|
3012
3505
|
)
|
|
3013
3506
|
}
|
|
3014
3507
|
),
|
|
3015
|
-
/* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts),
|
|
3508
|
+
/* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts), styles16.itemText, { color: t.colors.text }], children: text })
|
|
3016
3509
|
]
|
|
3017
3510
|
}
|
|
3018
3511
|
);
|
|
3019
3512
|
};
|
|
3020
|
-
var WorkItem =
|
|
3513
|
+
var WorkItem = React19.memo(_WorkItem);
|
|
3021
3514
|
var _InterstitialCard = ({
|
|
3022
3515
|
title,
|
|
3023
3516
|
body,
|
|
@@ -3054,7 +3547,7 @@ var _InterstitialCard = ({
|
|
|
3054
3547
|
toValue: 1,
|
|
3055
3548
|
duration: scaledMs(INTERSTITIAL_HEAD_MS),
|
|
3056
3549
|
delay,
|
|
3057
|
-
easing:
|
|
3550
|
+
easing: easeWire9,
|
|
3058
3551
|
useNativeDriver: true
|
|
3059
3552
|
});
|
|
3060
3553
|
const anims = [
|
|
@@ -3067,7 +3560,7 @@ var _InterstitialCard = ({
|
|
|
3067
3560
|
}, [reduced, artT, titleT, bodyT]);
|
|
3068
3561
|
const riseStyle = (value) => ({
|
|
3069
3562
|
opacity: value,
|
|
3070
|
-
transform: [{ translateY: value.interpolate({ inputRange: [0, 1], outputRange: [
|
|
3563
|
+
transform: [{ translateY: value.interpolate({ inputRange: [0, 1], outputRange: [RISE_PX2, 0] }) }]
|
|
3071
3564
|
});
|
|
3072
3565
|
const handleContinue = useCallback(() => {
|
|
3073
3566
|
if (submitted || !canAdvance) return;
|
|
@@ -3090,18 +3583,18 @@ var _InterstitialCard = ({
|
|
|
3090
3583
|
disabled: submitted || !canAdvance
|
|
3091
3584
|
}
|
|
3092
3585
|
),
|
|
3093
|
-
children: /* @__PURE__ */ jsxs(View, { style: [
|
|
3094
|
-
/* @__PURE__ */ jsx(Animated.View, { style: [
|
|
3586
|
+
children: /* @__PURE__ */ jsxs(View, { style: [styles16.center, { gap: t.spacing.md }], children: [
|
|
3587
|
+
/* @__PURE__ */ jsx(Animated.View, { style: [styles16.fullWidth, riseStyle(artT)], children: showImage ? /* @__PURE__ */ jsx(
|
|
3095
3588
|
Image,
|
|
3096
3589
|
{
|
|
3097
3590
|
source: { uri: imageUrl },
|
|
3098
|
-
style:
|
|
3591
|
+
style: styles16.image,
|
|
3099
3592
|
resizeMode: "contain",
|
|
3100
3593
|
onError: () => setImgFailed(true)
|
|
3101
3594
|
}
|
|
3102
|
-
) : art ? /* @__PURE__ */ jsx(View, { style:
|
|
3595
|
+
) : art ? /* @__PURE__ */ jsx(View, { style: styles16.art, children: art }) : null }),
|
|
3103
3596
|
/* @__PURE__ */ jsx(Animated.View, { style: riseStyle(titleT), children: /* @__PURE__ */ jsx(Text, { style: [headingStyle(t.fonts), { color: t.colors.text, textAlign: "center" }], children: title }) }),
|
|
3104
|
-
(items == null ? void 0 : items.length) ? /* @__PURE__ */ jsx(View, { style: [
|
|
3597
|
+
(items == null ? void 0 : items.length) ? /* @__PURE__ */ jsx(View, { style: [styles16.items, { gap: t.spacing.md, marginTop: t.spacing.sm }], children: items.map((item, i) => /* @__PURE__ */ jsx(WorkItem, { text: item, index: i, reduced }, `${item}-${i}`)) }) : body ? /* @__PURE__ */ jsx(Animated.View, { style: riseStyle(bodyT), children: /* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: body }) }) : null
|
|
3105
3598
|
] })
|
|
3106
3599
|
}
|
|
3107
3600
|
);
|
|
@@ -3109,11 +3602,11 @@ var _InterstitialCard = ({
|
|
|
3109
3602
|
var InterstitialCard = {
|
|
3110
3603
|
name: "InterstitialCard",
|
|
3111
3604
|
description: "Use ONCE mid-flow (never first, never last) as a momentum/value screen between questions \u2014 an illustration + a warm line that reflects answers back, like Duolingo/Cal-AI. NOT a question; the user just taps Continue. Set `illustration` to an app-registered artwork name. Prefer `items` (2-3 short ticked lines, each reflecting a real answer) when you have the signal. Skip it if you don't have enough signal to make the line specific.",
|
|
3112
|
-
component:
|
|
3113
|
-
propsSchema:
|
|
3605
|
+
component: React19.memo(_InterstitialCard),
|
|
3606
|
+
propsSchema: schema8,
|
|
3114
3607
|
defaultProps: { ctaLabel: "Continue", illustrationFallback: "momentum" }
|
|
3115
3608
|
};
|
|
3116
|
-
var
|
|
3609
|
+
var styles16 = StyleSheet.create({
|
|
3117
3610
|
center: { width: "100%", alignItems: "center", justifyContent: "center" },
|
|
3118
3611
|
fullWidth: { width: "100%", alignItems: "center" },
|
|
3119
3612
|
art: { alignItems: "center", justifyContent: "center" },
|
|
@@ -3311,6 +3804,33 @@ var collectDeviceContext = () => {
|
|
|
3311
3804
|
return ctx;
|
|
3312
3805
|
};
|
|
3313
3806
|
|
|
3807
|
+
// src/identity/identityRecord.ts
|
|
3808
|
+
var IDENTITY_PROVENANCE_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:identityProvenance");
|
|
3809
|
+
var provenanceGlobal = globalThis;
|
|
3810
|
+
var provenanceRegistry = () => {
|
|
3811
|
+
const existing = provenanceGlobal[IDENTITY_PROVENANCE_SLOT];
|
|
3812
|
+
if (existing) return existing;
|
|
3813
|
+
const created = { host: /* @__PURE__ */ new Map() };
|
|
3814
|
+
provenanceGlobal[IDENTITY_PROVENANCE_SLOT] = created;
|
|
3815
|
+
return created;
|
|
3816
|
+
};
|
|
3817
|
+
var provenanceKey = (space, scope) => `${space}:${scope != null ? scope : "default"}`;
|
|
3818
|
+
var resolveIdentity = (input) => {
|
|
3819
|
+
var _a2;
|
|
3820
|
+
if (typeof input.value !== "string") return void 0;
|
|
3821
|
+
const value = input.value.trim();
|
|
3822
|
+
if (!value) return void 0;
|
|
3823
|
+
const durable = (_a2 = input.durable) != null ? _a2 : input.source === "host";
|
|
3824
|
+
if (input.source === "host") {
|
|
3825
|
+
provenanceRegistry().host.set(provenanceKey(input.space, input.scope), value);
|
|
3826
|
+
}
|
|
3827
|
+
return { value, space: input.space, source: input.source, durable };
|
|
3828
|
+
};
|
|
3829
|
+
var hostIdentity = (space, scope) => provenanceRegistry().host.get(provenanceKey(space, scope));
|
|
3830
|
+
var resetIdentityProvenance = () => {
|
|
3831
|
+
provenanceRegistry().host.clear();
|
|
3832
|
+
};
|
|
3833
|
+
|
|
3314
3834
|
// src/context/deviceId.ts
|
|
3315
3835
|
var AUTO_DEVICE_ID_PREFIX = "wdev_";
|
|
3316
3836
|
var deviceIdStorageKey = (appId) => `wireai:analytics:deviceKey:${appId != null ? appId : "default"}`;
|
|
@@ -3333,24 +3853,37 @@ var startHydration = (registry, appId, storage, minted) => {
|
|
|
3333
3853
|
const existing = registry.pending.get(appId);
|
|
3334
3854
|
if (existing) return existing;
|
|
3335
3855
|
const slot = deviceIdStorageKey(appId);
|
|
3336
|
-
const
|
|
3856
|
+
const degraded = () => {
|
|
3337
3857
|
var _a2;
|
|
3338
|
-
return (_a2 = registry.keys.get(appId)) != null ? _a2 : minted;
|
|
3858
|
+
return { value: (_a2 = registry.keys.get(appId)) != null ? _a2 : minted, durable: false };
|
|
3339
3859
|
};
|
|
3860
|
+
const adopted = (value) => ({ value, durable: true });
|
|
3340
3861
|
let run;
|
|
3341
3862
|
try {
|
|
3342
3863
|
run = Promise.resolve(storage.getItem(slot)).then((saved) => {
|
|
3343
3864
|
const persisted = typeof saved === "string" && saved.trim() ? saved.trim() : void 0;
|
|
3344
3865
|
if (persisted) {
|
|
3345
3866
|
registry.keys.set(appId, persisted);
|
|
3346
|
-
return persisted;
|
|
3867
|
+
return adopted(persisted);
|
|
3347
3868
|
}
|
|
3348
|
-
return Promise.resolve(storage.setItem(slot, minted)).then(
|
|
3349
|
-
|
|
3869
|
+
return Promise.resolve(storage.setItem(slot, minted)).then(
|
|
3870
|
+
() => {
|
|
3871
|
+
var _a2;
|
|
3872
|
+
return adopted((_a2 = registry.keys.get(appId)) != null ? _a2 : minted);
|
|
3873
|
+
},
|
|
3874
|
+
degraded
|
|
3875
|
+
);
|
|
3876
|
+
}).catch(degraded);
|
|
3350
3877
|
} catch {
|
|
3351
|
-
run = Promise.resolve(
|
|
3878
|
+
run = Promise.resolve(degraded());
|
|
3352
3879
|
}
|
|
3353
3880
|
registry.pending.set(appId, run);
|
|
3881
|
+
void run.then((outcome) => {
|
|
3882
|
+
var _a2;
|
|
3883
|
+
if (outcome.durable) return;
|
|
3884
|
+
(_a2 = registry.pending) == null ? void 0 : _a2.delete(appId);
|
|
3885
|
+
registry.hydrating.delete(appId);
|
|
3886
|
+
});
|
|
3354
3887
|
return run;
|
|
3355
3888
|
};
|
|
3356
3889
|
var resolveAutoDeviceKey = (opts = {}) => {
|
|
@@ -3370,14 +3903,20 @@ var resolveAutoDeviceKey = (opts = {}) => {
|
|
|
3370
3903
|
return (_b = registry.keys.get(appId)) != null ? _b : id;
|
|
3371
3904
|
};
|
|
3372
3905
|
var hydrateAutoDeviceKey = async (opts = {}) => {
|
|
3906
|
+
var _a2, _b;
|
|
3907
|
+
return (_b = (_a2 = await hydrateDeviceIdentity(opts)) == null ? void 0 : _a2.value) != null ? _b : resolveAutoDeviceKey(opts);
|
|
3908
|
+
};
|
|
3909
|
+
var hydrateDeviceIdentity = async (opts = {}) => {
|
|
3373
3910
|
var _a2, _b, _c;
|
|
3374
3911
|
const id = resolveAutoDeviceKey(opts);
|
|
3375
|
-
if (!opts.storage) return id;
|
|
3376
|
-
const registry = autoDeviceKeyRegistry();
|
|
3377
3912
|
const appId = (_a2 = opts.appId) != null ? _a2 : "default";
|
|
3913
|
+
const record = (value, durable) => resolveIdentity({ value, space: "device", source: "auto", durable, scope: appId });
|
|
3914
|
+
if (!opts.storage) return record(id, false);
|
|
3915
|
+
const registry = autoDeviceKeyRegistry();
|
|
3378
3916
|
const pending = (_b = registry.pending) == null ? void 0 : _b.get(appId);
|
|
3379
|
-
if (pending)
|
|
3380
|
-
|
|
3917
|
+
if (!pending) return record((_c = registry.keys.get(appId)) != null ? _c : id, false);
|
|
3918
|
+
const outcome = await pending;
|
|
3919
|
+
return record(outcome.value, outcome.durable);
|
|
3381
3920
|
};
|
|
3382
3921
|
var resetAutoDeviceKeys = () => {
|
|
3383
3922
|
var _a2;
|
|
@@ -3401,13 +3940,6 @@ var getCurrentSessionId = () => globalSlot[CURRENT_SESSION_ID_SLOT];
|
|
|
3401
3940
|
var resetCurrentSessionId = () => {
|
|
3402
3941
|
globalSlot[CURRENT_SESSION_ID_SLOT] = void 0;
|
|
3403
3942
|
};
|
|
3404
|
-
var warnInDev = (message) => {
|
|
3405
|
-
if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
|
|
3406
|
-
console.warn(message);
|
|
3407
|
-
return true;
|
|
3408
|
-
}
|
|
3409
|
-
return false;
|
|
3410
|
-
};
|
|
3411
3943
|
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.";
|
|
3412
3944
|
var MINT_WARNED_SLOT = /* @__PURE__ */ Symbol.for(
|
|
3413
3945
|
"@wireai/activation:currentSessionIdMintWarned"
|
|
@@ -3504,27 +4036,20 @@ var identifyOnboarding = async (opts) => {
|
|
|
3504
4036
|
contextId = stored == null ? void 0 : stored.id;
|
|
3505
4037
|
}
|
|
3506
4038
|
}
|
|
3507
|
-
|
|
4039
|
+
let space = "onboarding";
|
|
4040
|
+
if (!contextId && opts.allowAppSessionFallback) {
|
|
4041
|
+
contextId = getCurrentSessionId();
|
|
4042
|
+
space = "app_session";
|
|
4043
|
+
}
|
|
3508
4044
|
if (!contextId) return false;
|
|
3509
4045
|
reportClientEvent(
|
|
3510
4046
|
{ serverUrl: opts.config.serverUrl, apiKey: opts.config.apiKey },
|
|
3511
4047
|
{ event_type: "identify", session_id: contextId, user_id: userId }
|
|
3512
4048
|
);
|
|
3513
|
-
return
|
|
4049
|
+
return space;
|
|
3514
4050
|
};
|
|
3515
4051
|
|
|
3516
4052
|
// src/context/userContext.ts
|
|
3517
|
-
var RESERVED_USER_CONTEXT_KEYS = [
|
|
3518
|
-
"device_key",
|
|
3519
|
-
"app_version",
|
|
3520
|
-
"app_build",
|
|
3521
|
-
"network_type",
|
|
3522
|
-
"session_count",
|
|
3523
|
-
"returning",
|
|
3524
|
-
"platform",
|
|
3525
|
-
"user_email",
|
|
3526
|
-
"user_email_hashed"
|
|
3527
|
-
];
|
|
3528
4053
|
var EXTRA_KEY_PREFIX = "custom.";
|
|
3529
4054
|
var isWireScalar = (value) => {
|
|
3530
4055
|
const t = typeof value;
|
|
@@ -3605,9 +4130,41 @@ var resolveUserContext = (ctx = {}, opts = {}) => {
|
|
|
3605
4130
|
if (Object.keys(bucket).length > 0) result.userContext = bucket;
|
|
3606
4131
|
return result;
|
|
3607
4132
|
};
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
4133
|
+
|
|
4134
|
+
// src/permissions/permissionMemory.ts
|
|
4135
|
+
var permissionStorageKey = (appId) => `wireai:permissions:${appId}`;
|
|
4136
|
+
var readSettledPermissions = (raw, sessionId) => {
|
|
4137
|
+
if (!raw || !sessionId) return [];
|
|
4138
|
+
try {
|
|
4139
|
+
const parsed = JSON.parse(raw);
|
|
4140
|
+
if (typeof parsed !== "object" || parsed === null) return [];
|
|
4141
|
+
const record = parsed;
|
|
4142
|
+
if (record.sessionId !== sessionId || !Array.isArray(record.ids)) return [];
|
|
4143
|
+
return record.ids.filter((id) => typeof id === "string" && id.length > 0);
|
|
4144
|
+
} catch {
|
|
4145
|
+
return [];
|
|
4146
|
+
}
|
|
4147
|
+
};
|
|
4148
|
+
var loadSettledPermissions = async (storage, key, sessionId) => {
|
|
4149
|
+
try {
|
|
4150
|
+
return readSettledPermissions(await withTimeout(storage.getItem(key), READ_TIMEOUT_MS), sessionId);
|
|
4151
|
+
} catch {
|
|
4152
|
+
return [];
|
|
4153
|
+
}
|
|
4154
|
+
};
|
|
4155
|
+
var saveSettledPermissions = (storage, key, sessionId, ids) => {
|
|
4156
|
+
try {
|
|
4157
|
+
const record = { sessionId, ids: [...ids] };
|
|
4158
|
+
void storage.setItem(key, JSON.stringify(record)).catch(() => {
|
|
4159
|
+
});
|
|
4160
|
+
} catch {
|
|
4161
|
+
}
|
|
4162
|
+
};
|
|
4163
|
+
var clearSettledPermissions = (storage, key) => {
|
|
4164
|
+
try {
|
|
4165
|
+
void storage.removeItem(key).catch(() => {
|
|
4166
|
+
});
|
|
4167
|
+
} catch {
|
|
3611
4168
|
}
|
|
3612
4169
|
};
|
|
3613
4170
|
var AUTO_JOIN_HYDRATION_TIMEOUT_MS = 1500;
|
|
@@ -3634,9 +4191,10 @@ var WireOnboarding = ({
|
|
|
3634
4191
|
retainSessionOnComplete,
|
|
3635
4192
|
userContext,
|
|
3636
4193
|
userId,
|
|
3637
|
-
autoJoinKey = true
|
|
4194
|
+
autoJoinKey = true,
|
|
4195
|
+
permissionScreens
|
|
3638
4196
|
}) => {
|
|
3639
|
-
var _a2, _b, _c;
|
|
4197
|
+
var _a2, _b, _c, _d, _e, _f;
|
|
3640
4198
|
const boundUserId = useMemo(() => sanitizeUserId(userId), [userId]);
|
|
3641
4199
|
const device = useMemo(() => {
|
|
3642
4200
|
const collected = collectDeviceContext();
|
|
@@ -3647,44 +4205,72 @@ var WireOnboarding = ({
|
|
|
3647
4205
|
() => userContextKey ? JSON.parse(userContextKey) : void 0,
|
|
3648
4206
|
[userContextKey]
|
|
3649
4207
|
);
|
|
3650
|
-
const
|
|
4208
|
+
const hostJoinIdentity = useMemo(
|
|
4209
|
+
() => resolveIdentity({
|
|
4210
|
+
value: userContextStable == null ? void 0 : userContextStable.device_key,
|
|
4211
|
+
space: "device",
|
|
4212
|
+
source: "host",
|
|
4213
|
+
scope: config.appId
|
|
4214
|
+
}),
|
|
4215
|
+
[userContextStable, config.appId]
|
|
4216
|
+
);
|
|
4217
|
+
const missingJoinKey = !hostJoinIdentity;
|
|
3651
4218
|
const autoJoinPossible = Boolean(storage) && autoJoinKey !== false;
|
|
3652
4219
|
const wantsAutoJoin = missingJoinKey && autoJoinPossible;
|
|
3653
|
-
const [
|
|
4220
|
+
const [autoJoinOutcome, setAutoJoinOutcome] = useState(void 0);
|
|
3654
4221
|
useEffect(() => {
|
|
3655
|
-
if (!wantsAutoJoin ||
|
|
4222
|
+
if (!wantsAutoJoin || autoJoinOutcome !== void 0 || !storage) return;
|
|
3656
4223
|
let cancelled = false;
|
|
3657
4224
|
const timer = setTimeout(() => {
|
|
3658
|
-
if (!cancelled)
|
|
4225
|
+
if (!cancelled) setAutoJoinOutcome({ key: null, reason: "timeout" });
|
|
3659
4226
|
}, AUTO_JOIN_HYDRATION_TIMEOUT_MS);
|
|
3660
|
-
void
|
|
4227
|
+
void hydrateDeviceIdentity({ appId: config.appId, storage }).then((identity) => {
|
|
3661
4228
|
if (cancelled) return;
|
|
3662
4229
|
clearTimeout(timer);
|
|
3663
|
-
|
|
4230
|
+
setAutoJoinOutcome(
|
|
4231
|
+
(identity == null ? void 0 : identity.durable) ? { key: identity.value } : { key: null, reason: "not-durable" }
|
|
4232
|
+
);
|
|
3664
4233
|
});
|
|
3665
4234
|
return () => {
|
|
3666
4235
|
cancelled = true;
|
|
3667
4236
|
clearTimeout(timer);
|
|
3668
4237
|
};
|
|
3669
|
-
}, [wantsAutoJoin,
|
|
3670
|
-
const injectedJoinKey = wantsAutoJoin
|
|
3671
|
-
const autoJoinPending = wantsAutoJoin &&
|
|
4238
|
+
}, [wantsAutoJoin, autoJoinOutcome, config.appId, storage]);
|
|
4239
|
+
const injectedJoinKey = wantsAutoJoin ? (_a2 = autoJoinOutcome == null ? void 0 : autoJoinOutcome.key) != null ? _a2 : void 0 : void 0;
|
|
4240
|
+
const autoJoinPending = wantsAutoJoin && autoJoinOutcome === void 0;
|
|
3672
4241
|
const effectiveUserContext = useMemo(
|
|
3673
4242
|
() => injectedJoinKey ? { ...userContextStable, ...activationJoinContext(injectedJoinKey) } : userContextStable,
|
|
3674
4243
|
[userContextStable, injectedJoinKey]
|
|
3675
4244
|
);
|
|
4245
|
+
const frozenWireContextRef = useRef(null);
|
|
4246
|
+
if (!autoJoinPending && frozenWireContextRef.current === null) {
|
|
4247
|
+
frozenWireContextRef.current = effectiveUserContext ? JSON.stringify(effectiveUserContext) : "";
|
|
4248
|
+
}
|
|
4249
|
+
const startupUserContextKey = (_b = frozenWireContextRef.current) != null ? _b : "";
|
|
4250
|
+
const startupUserContext = useMemo(
|
|
4251
|
+
() => startupUserContextKey ? JSON.parse(startupUserContextKey) : void 0,
|
|
4252
|
+
[startupUserContextKey]
|
|
4253
|
+
);
|
|
3676
4254
|
const warnMissingJoinKey = missingJoinKey && !injectedJoinKey && !autoJoinPending;
|
|
3677
|
-
const autoJoinReason = autoJoinKey === false ? "you passed autoJoinKey={false}." : !storage ? "it got no `storage` prop, and without persistence its key would be different on every launch, which corrupts min_sessions instead of merely leaving the join empty." : "your `storage` adapter did not answer in time.";
|
|
4255
|
+
const autoJoinReason = autoJoinKey === false ? "you passed autoJoinKey={false}." : !storage ? "it got no `storage` prop, and without persistence its key would be different on every launch, which corrupts min_sessions instead of merely leaving the join empty." : (autoJoinOutcome == null ? void 0 : autoJoinOutcome.key) === null && autoJoinOutcome.reason === "not-durable" ? "your `storage` adapter answered but could not persist the key (the read or the write failed), so the key would be different on every launch, which corrupts min_sessions instead of merely leaving the join empty. Check the adapter you passed as `storage`." : "your `storage` adapter did not answer in time.";
|
|
3678
4256
|
useEffect(() => {
|
|
3679
4257
|
if (!warnMissingJoinKey) return;
|
|
3680
|
-
|
|
4258
|
+
warnInDev(
|
|
3681
4259
|
"[wireai] <WireOnboarding> got no user_context.device_key, so this onboarding session can never be joined to the app's later events and the `activated` funnel will read zero. Pass userContext={activationJoinContext(deviceKey)} \u2014 and if your app owns no device id, userContext={activationJoinContext(resolveAutoDeviceKey({ appId, storage }))} returns the SAME id the analytics side stamps. Never hand-write userContext={{ deviceKey }}. The kit did NOT auto-inject its own key here because " + autoJoinReason
|
|
3682
4260
|
);
|
|
3683
4261
|
}, [warnMissingJoinKey, autoJoinReason]);
|
|
4262
|
+
const hostKeyElsewhere = missingJoinKey ? hostIdentity("device", config.appId) : void 0;
|
|
4263
|
+
const warnHostKeyElsewhere = Boolean(hostKeyElsewhere) && Boolean(injectedJoinKey);
|
|
4264
|
+
useEffect(() => {
|
|
4265
|
+
if (!warnHostKeyElsewhere) return;
|
|
4266
|
+
warnInDev(
|
|
4267
|
+
`[wireai] <WireOnboarding> got no user_context.device_key, so the kit injected its own \u2014 but another surface in this app was constructed WITH a host-supplied device key ("${hostKeyElsewhere}"). Your app events carry that id and this onboarding session carries the kit's, so the two land in disjoint id spaces and the \`activated\` funnel still reads zero. Pass the SAME id here: userContext={activationJoinContext(deviceKey)}.`
|
|
4268
|
+
);
|
|
4269
|
+
}, [warnHostKeyElsewhere, hostKeyElsewhere]);
|
|
3684
4270
|
const misspelledJoinKey = missingJoinKey && typeof (userContextStable == null ? void 0 : userContextStable.deviceKey) === "string" && Boolean(userContextStable.deviceKey.trim());
|
|
3685
4271
|
useEffect(() => {
|
|
3686
4272
|
if (!misspelledJoinKey || !injectedJoinKey) return;
|
|
3687
|
-
|
|
4273
|
+
warnInDev(
|
|
3688
4274
|
"[wireai] <WireOnboarding> got userContext={{ deviceKey }}, which is NOT the wire key \u2014 the server's device lookup reads `device_key`. The kit auto-injected its own device_key so this session is at least self-consistent, but your app's own events carry YOUR id, so the two still will not join and the `activated` funnel stays zero. Pass userContext={activationJoinContext(deviceKey)} instead."
|
|
3689
4275
|
);
|
|
3690
4276
|
}, [misspelledJoinKey, injectedJoinKey]);
|
|
@@ -3696,6 +4282,7 @@ var WireOnboarding = ({
|
|
|
3696
4282
|
() => storage ? null : { id: makeSessionId(), resumed: false }
|
|
3697
4283
|
);
|
|
3698
4284
|
const storageKey = persistKey != null ? persistKey : sessionStorageKey(config.appId);
|
|
4285
|
+
const permissionsKey = permissionStorageKey(config.appId);
|
|
3699
4286
|
useEffect(() => {
|
|
3700
4287
|
if (!storage || session) return;
|
|
3701
4288
|
let cancelled = false;
|
|
@@ -3708,12 +4295,43 @@ var WireOnboarding = ({
|
|
|
3708
4295
|
}, [storage, storageKey, sessionTtlMs, session]);
|
|
3709
4296
|
const handleComplete = useCallback(
|
|
3710
4297
|
(result) => {
|
|
3711
|
-
if (storage && shouldClearOnComplete(retainSessionOnComplete))
|
|
4298
|
+
if (storage && shouldClearOnComplete(retainSessionOnComplete)) {
|
|
4299
|
+
clearPersistedSession(storage, storageKey);
|
|
4300
|
+
clearSettledPermissions(storage, permissionsKey);
|
|
4301
|
+
}
|
|
3712
4302
|
onComplete(result);
|
|
3713
4303
|
},
|
|
3714
|
-
[storage, storageKey, retainSessionOnComplete, onComplete]
|
|
4304
|
+
[storage, storageKey, permissionsKey, retainSessionOnComplete, onComplete]
|
|
4305
|
+
);
|
|
4306
|
+
const sessionId = (_c = session == null ? void 0 : session.id) != null ? _c : "";
|
|
4307
|
+
const wantsPermissionMemory = Boolean(storage) && ((_d = permissionScreens == null ? void 0 : permissionScreens.length) != null ? _d : 0) > 0;
|
|
4308
|
+
const [settledPermissions, setSettledPermissions] = useState(void 0);
|
|
4309
|
+
useEffect(() => {
|
|
4310
|
+
if (!wantsPermissionMemory || !storage || !sessionId || settledPermissions !== void 0) return;
|
|
4311
|
+
let cancelled = false;
|
|
4312
|
+
void loadSettledPermissions(storage, permissionsKey, sessionId).then((ids) => {
|
|
4313
|
+
if (!cancelled) setSettledPermissions(ids);
|
|
4314
|
+
});
|
|
4315
|
+
return () => {
|
|
4316
|
+
cancelled = true;
|
|
4317
|
+
};
|
|
4318
|
+
}, [wantsPermissionMemory, storage, permissionsKey, sessionId, settledPermissions]);
|
|
4319
|
+
const settledPermissionsRef = useRef(void 0);
|
|
4320
|
+
settledPermissionsRef.current = settledPermissions;
|
|
4321
|
+
const handlePermissionSettled = useCallback(
|
|
4322
|
+
(id) => {
|
|
4323
|
+
var _a3;
|
|
4324
|
+
const current = (_a3 = settledPermissionsRef.current) != null ? _a3 : [];
|
|
4325
|
+
if (current.includes(id)) return;
|
|
4326
|
+
const next = [...current, id];
|
|
4327
|
+
settledPermissionsRef.current = next;
|
|
4328
|
+
setSettledPermissions(next);
|
|
4329
|
+
if (storage && wantsPermissionMemory && sessionId) {
|
|
4330
|
+
saveSettledPermissions(storage, permissionsKey, sessionId, next);
|
|
4331
|
+
}
|
|
4332
|
+
},
|
|
4333
|
+
[storage, wantsPermissionMemory, permissionsKey, sessionId]
|
|
3715
4334
|
);
|
|
3716
|
-
const sessionId = (_a2 = session == null ? void 0 : session.id) != null ? _a2 : "";
|
|
3717
4335
|
const startupUserIdRef = useRef(boundUserId);
|
|
3718
4336
|
const metadataKey = config.metadata ? JSON.stringify(config.metadata) : "";
|
|
3719
4337
|
const metadataStable = useMemo(() => metadataKey ? JSON.parse(metadataKey) : void 0, [metadataKey]);
|
|
@@ -3737,7 +4355,9 @@ var WireOnboarding = ({
|
|
|
3737
4355
|
// Device snapshot + host-injected user context ride the session-start metadata so the
|
|
3738
4356
|
// backend can segment the funnel. Old servers ignore these unknown keys (backward compat).
|
|
3739
4357
|
device,
|
|
3740
|
-
|
|
4358
|
+
// The FROZEN wire context, never the live one — see the freeze note above. A change here
|
|
4359
|
+
// recreates the A2A adapter and drops `contextId`.
|
|
4360
|
+
...startupUserContext ? { userContext: startupUserContext } : {},
|
|
3741
4361
|
// The session-start user id (from the ref) rides the session-start metadata so the
|
|
3742
4362
|
// server binds the session to a real user at creation. Reading the ref — not
|
|
3743
4363
|
// `boundUserId` — keeps this memo off the userId dependency, so a mid-session change
|
|
@@ -3746,7 +4366,7 @@ var WireOnboarding = ({
|
|
|
3746
4366
|
},
|
|
3747
4367
|
timeoutMs: 6e4
|
|
3748
4368
|
};
|
|
3749
|
-
}, [config.serverUrl, config.appId, config.apiKey, metadataStable, sessionId, componentsStable, device,
|
|
4369
|
+
}, [config.serverUrl, config.appId, config.apiKey, metadataStable, sessionId, componentsStable, device, startupUserContext]);
|
|
3750
4370
|
const reportTarget = useMemo(
|
|
3751
4371
|
() => ({ serverUrl: config.serverUrl, apiKey: config.apiKey }),
|
|
3752
4372
|
[config.serverUrl, config.apiKey]
|
|
@@ -3772,8 +4392,8 @@ var WireOnboarding = ({
|
|
|
3772
4392
|
return /* @__PURE__ */ jsx(OnboardingThemeProvider, { theme, children: /* @__PURE__ */ jsx(
|
|
3773
4393
|
LoadingScreen,
|
|
3774
4394
|
{
|
|
3775
|
-
title: (
|
|
3776
|
-
hint: (
|
|
4395
|
+
title: (_e = copy == null ? void 0 : copy.restoringTitle) != null ? _e : DEFAULT_COPY.restoringTitle,
|
|
4396
|
+
hint: (_f = copy == null ? void 0 : copy.restoringHint) != null ? _f : DEFAULT_COPY.restoringHint
|
|
3777
4397
|
}
|
|
3778
4398
|
) });
|
|
3779
4399
|
}
|
|
@@ -3794,7 +4414,11 @@ var WireOnboarding = ({
|
|
|
3794
4414
|
reportTarget,
|
|
3795
4415
|
sessionId,
|
|
3796
4416
|
resumed: session.resumed,
|
|
3797
|
-
clientContext
|
|
4417
|
+
clientContext,
|
|
4418
|
+
permissionScreens,
|
|
4419
|
+
settledPermissions,
|
|
4420
|
+
permissionsPending: wantsPermissionMemory && settledPermissions === void 0,
|
|
4421
|
+
onPermissionSettled: handlePermissionSettled
|
|
3798
4422
|
}
|
|
3799
4423
|
) }) }) }) });
|
|
3800
4424
|
};
|
|
@@ -3923,20 +4547,6 @@ var themeFromBrand = (input) => {
|
|
|
3923
4547
|
button: { ...base.button, ...extra.button }
|
|
3924
4548
|
};
|
|
3925
4549
|
};
|
|
3926
|
-
var _DoneBlock = ({
|
|
3927
|
-
title = "You're all set",
|
|
3928
|
-
message = "Personalizing your experience\u2026"
|
|
3929
|
-
}) => {
|
|
3930
|
-
const t = useOnboardingTheme();
|
|
3931
|
-
return /* @__PURE__ */ jsxs(View, { style: [styles16.center, { gap: t.spacing.sm, padding: t.spacing.lg }], children: [
|
|
3932
|
-
/* @__PURE__ */ jsx(Text, { style: [headingStyle(t.fonts), { color: t.colors.success, textAlign: "center" }], children: title }),
|
|
3933
|
-
/* @__PURE__ */ jsx(Text, { style: [bodyStyle(t.fonts), { color: t.colors.textMuted, textAlign: "center" }], children: message })
|
|
3934
|
-
] });
|
|
3935
|
-
};
|
|
3936
|
-
var DoneBlock = React18.memo(_DoneBlock);
|
|
3937
|
-
var styles16 = StyleSheet.create({
|
|
3938
|
-
center: { flex: 1, alignItems: "center", justifyContent: "center" }
|
|
3939
|
-
});
|
|
3940
4550
|
|
|
3941
4551
|
// src/motion/centeredModalMotion.ts
|
|
3942
4552
|
var CENTERED_MODAL_ANIM = {
|
|
@@ -4052,7 +4662,7 @@ var _CenteredModal = forwardRef(
|
|
|
4052
4662
|
}
|
|
4053
4663
|
);
|
|
4054
4664
|
_CenteredModal.displayName = "CenteredModal";
|
|
4055
|
-
var CenteredModal =
|
|
4665
|
+
var CenteredModal = React19.memo(_CenteredModal);
|
|
4056
4666
|
var styles17 = StyleSheet.create({
|
|
4057
4667
|
fill: { flex: 1 },
|
|
4058
4668
|
backdrop: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#000" },
|
|
@@ -4239,11 +4849,6 @@ var WIRE_ENV_VARS = [
|
|
|
4239
4849
|
"EXPO_PUBLIC_WIREAI_SERVER_URL",
|
|
4240
4850
|
"EXPO_PUBLIC_WIREAI_APP_ID"
|
|
4241
4851
|
];
|
|
4242
|
-
var warnInDev3 = (message) => {
|
|
4243
|
-
if (typeof __DEV__ !== "undefined" && __DEV__ && typeof console !== "undefined" && console.warn) {
|
|
4244
|
-
console.warn(message);
|
|
4245
|
-
}
|
|
4246
|
-
};
|
|
4247
4852
|
var wireConfigFromEnv = (overrides) => {
|
|
4248
4853
|
var _a2, _b, _c, _d, _e;
|
|
4249
4854
|
const apiKey = (_a2 = overrides == null ? void 0 : overrides.apiKey) != null ? _a2 : process.env.EXPO_PUBLIC_WIREAI_API_KEY;
|
|
@@ -4254,7 +4859,7 @@ var wireConfigFromEnv = (overrides) => {
|
|
|
4254
4859
|
apiKey ? void 0 : "EXPO_PUBLIC_WIREAI_API_KEY",
|
|
4255
4860
|
serverUrl ? void 0 : "EXPO_PUBLIC_WIREAI_SERVER_URL"
|
|
4256
4861
|
].filter(Boolean);
|
|
4257
|
-
|
|
4862
|
+
warnInDev(
|
|
4258
4863
|
`[wireai] wireConfigFromEnv() returned null: ${missing.join(" and ")} ${missing.length > 1 ? "are" : "is"} missing. The kit is now FULLY DISABLED (no onboarding, no analytics, no gates) and nothing else will report an error. Set the var(s) in your env / EAS secrets.`
|
|
4259
4864
|
);
|
|
4260
4865
|
return null;
|
|
@@ -4309,6 +4914,11 @@ var toAnalyticsEvent = (event) => {
|
|
|
4309
4914
|
};
|
|
4310
4915
|
case "fallback":
|
|
4311
4916
|
return { name: WIRE_ONBOARDING_EVENTS.fallback, params: { reason: event.reason } };
|
|
4917
|
+
case "permission":
|
|
4918
|
+
return {
|
|
4919
|
+
name: permissionEventName(event.stage),
|
|
4920
|
+
params: permissionEventProps(event.permission, event.status)
|
|
4921
|
+
};
|
|
4312
4922
|
default: {
|
|
4313
4923
|
const _exhaustive = event;
|
|
4314
4924
|
return _exhaustive;
|
|
@@ -4362,15 +4972,17 @@ var resetActivationRevalidation = () => {
|
|
|
4362
4972
|
};
|
|
4363
4973
|
|
|
4364
4974
|
// src/activation/wireActivation.ts
|
|
4365
|
-
var clean =
|
|
4366
|
-
if (typeof value !== "string") return void 0;
|
|
4367
|
-
const trimmed = value.trim();
|
|
4368
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
4369
|
-
};
|
|
4975
|
+
var clean = cleanString;
|
|
4370
4976
|
var createWireActivation = (config) => {
|
|
4371
4977
|
var _a2, _b;
|
|
4372
4978
|
const target = { serverUrl: config.serverUrl, apiKey: config.apiKey };
|
|
4373
4979
|
const explicitDeviceKey = (_b = clean(config.deviceKey)) != null ? _b : clean((_a2 = config.userContext) == null ? void 0 : _a2.deviceKey);
|
|
4980
|
+
resolveIdentity({
|
|
4981
|
+
value: explicitDeviceKey,
|
|
4982
|
+
space: "device",
|
|
4983
|
+
source: "host",
|
|
4984
|
+
scope: config.appId
|
|
4985
|
+
});
|
|
4374
4986
|
const autoDeviceKeyOptions = {
|
|
4375
4987
|
appId: config.appId,
|
|
4376
4988
|
// An explicit key opts out of minting AND persisting (unchanged contract).
|
|
@@ -4804,6 +5416,30 @@ var DEFAULTS = {
|
|
|
4804
5416
|
maxRetries: 6
|
|
4805
5417
|
};
|
|
4806
5418
|
var READ_TIMEOUT_MS4 = 1500;
|
|
5419
|
+
var QUEUE_KEY_SLOT = /* @__PURE__ */ Symbol.for("@wireai/activation:eventQueueKeys");
|
|
5420
|
+
var queueKeyGlobal = globalThis;
|
|
5421
|
+
var claimedQueueKeys = () => {
|
|
5422
|
+
const existing = queueKeyGlobal[QUEUE_KEY_SLOT];
|
|
5423
|
+
if (existing) return existing;
|
|
5424
|
+
const created = /* @__PURE__ */ new Set();
|
|
5425
|
+
queueKeyGlobal[QUEUE_KEY_SLOT] = created;
|
|
5426
|
+
return created;
|
|
5427
|
+
};
|
|
5428
|
+
var claimQueueKey = (preferred, explicit) => {
|
|
5429
|
+
const claimed = claimedQueueKeys();
|
|
5430
|
+
if (explicit || !claimed.has(preferred)) {
|
|
5431
|
+
claimed.add(preferred);
|
|
5432
|
+
return preferred;
|
|
5433
|
+
}
|
|
5434
|
+
let ordinal = 2;
|
|
5435
|
+
while (claimed.has(`${preferred}#${ordinal}`)) ordinal++;
|
|
5436
|
+
const key = `${preferred}#${ordinal}`;
|
|
5437
|
+
claimed.add(key);
|
|
5438
|
+
warnInDev(
|
|
5439
|
+
`[wireai] a second event queue was created for the same appId, and "${preferred}" is already claimed by a live one. Two queues sharing one storage slot overwrite each other's backlog, delete each other's pending events when one drains to empty, and double-send on relaunch, so this queue was given "${key}" instead. Prefer ONE analytics instance per app; if you really need two, pass an explicit \`storageKey\` to each so the slots are yours to reason about.`
|
|
5440
|
+
);
|
|
5441
|
+
return key;
|
|
5442
|
+
};
|
|
4807
5443
|
var withTimeout4 = (p, ms) => {
|
|
4808
5444
|
let timer;
|
|
4809
5445
|
const timeout = new Promise((resolve) => {
|
|
@@ -4835,7 +5471,8 @@ var createEventQueue = (options) => {
|
|
|
4835
5471
|
var _a2, _b, _c, _d, _e, _f, _g;
|
|
4836
5472
|
const target = options.target;
|
|
4837
5473
|
const storage = options.storage;
|
|
4838
|
-
const
|
|
5474
|
+
const defaultKey = (_b = options.storageKey) != null ? _b : `wireai:evtq:${(_a2 = options.appId) != null ? _a2 : "default"}`;
|
|
5475
|
+
const key = storage ? claimQueueKey(defaultKey, options.storageKey !== void 0) : defaultKey;
|
|
4839
5476
|
const maxSize = (_c = options.maxSize) != null ? _c : DEFAULTS.maxSize;
|
|
4840
5477
|
const batchSize = (_d = options.batchSize) != null ? _d : DEFAULTS.batchSize;
|
|
4841
5478
|
const baseBackoffMs = (_e = options.baseBackoffMs) != null ? _e : DEFAULTS.baseBackoffMs;
|
|
@@ -5025,7 +5662,13 @@ var useLifecycleEvents = (config, options = {}) => {
|
|
|
5025
5662
|
return (cfg == null ? void 0 : cfg.serverUrl) ? { serverUrl: cfg.serverUrl, apiKey: (_a2 = cfg.apiKey) != null ? _a2 : "" } : void 0;
|
|
5026
5663
|
};
|
|
5027
5664
|
const resolveDeviceKey = (cfg, opts) => {
|
|
5028
|
-
|
|
5665
|
+
var _a2;
|
|
5666
|
+
const host = (_a2 = resolveIdentity({
|
|
5667
|
+
value: opts.deviceKey,
|
|
5668
|
+
space: "device",
|
|
5669
|
+
source: "host",
|
|
5670
|
+
scope: cfg == null ? void 0 : cfg.appId
|
|
5671
|
+
})) == null ? void 0 : _a2.value;
|
|
5029
5672
|
if (host) return host;
|
|
5030
5673
|
if (!(cfg == null ? void 0 : cfg.storage)) return void 0;
|
|
5031
5674
|
return resolveAutoDeviceKey({ appId: cfg.appId, storage: cfg.storage });
|
|
@@ -5103,6 +5746,6 @@ var useLifecycleEvents = (config, options = {}) => {
|
|
|
5103
5746
|
}, []);
|
|
5104
5747
|
};
|
|
5105
5748
|
|
|
5106
|
-
export { AUTO_DEVICE_ID_PREFIX, AnimatedSparkle, BACKGROUND_SESSION_MS, CardGridSelectCard, CardHandoff, CenteredModal, ChipSelectCard, CompletionView, DEFAULT_FEATURES_TTL_MS, DEFAULT_SESSION_TTL_MS, DemoOnboarding,
|
|
5749
|
+
export { AUTO_DEVICE_ID_PREFIX, AnimatedSparkle, BACKGROUND_SESSION_MS, CardGridSelectCard, CardHandoff, CenteredModal, ChipSelectCard, CompletionView, DEFAULT_FEATURES_TTL_MS, DEFAULT_PERMISSION_COPY, DEFAULT_PERMISSION_PLACEMENT, DEFAULT_SESSION_TTL_MS, DemoOnboarding, EXTRA_KEY_PREFIX, ErrorBlock, FIRST_OPEN_EVENT, GENERIC_PERMISSION_COPY, IconRegistryProvider, IllustrationProvider, InterstitialCard, LoadingBlock, LoadingScreen, NOTIFICATIONS_PERMISSION_COPY, NumberStepperCard, Button as OnboardingButton, OnboardingFlow, OnboardingScaffold, OnboardingThemeProvider, PERMISSION_CARD_NAME, PLAN_TIER_CONTEXT_KEY, PermissionCard, PermissionCardView, SESSION_STARTED_EVENT, SelectionCard, StatusCard, StepProgress, TextInputCard, USER_ID_MAX_LENGTH, WIRE_ENV_VARS, WIRE_ICON_GLYPHS, WIRE_ICON_NAMES, WIRE_ONBOARDING_EVENTS, WIRE_PERMISSION_EVENTS, WIRE_PURCHASE_EVENTS, WireFeaturesProvider, WireIcon, WireOnboarding, activationJoinContext, activeEntitlement, analyticsUserIdStorageKey, attributionMetadata, bumpActivationRevalidation, clearPersistedSession, clearPiiFromContext, clearSettledPermissions, clearUserContext, collectDeviceContext, createRevenueCatBridge, createWireActivation, defaultIllustrations, defaultOnboardingTheme, defaultWireFeatures, deriveAnswers, describeEntitlement, describeFailure, describePackage, detectAppVersion, detectNativeModel, deviceIdStorageKey, ensureCurrentSessionId, featuresCacheKey, featuresEqual, fetchWireFeatures, firstOpenStorageKey, getActivationRevalidationVersion, getCurrentSessionId, hashEmailFnv1a, hostIdentity, hydrateAutoDeviceKey, hydrateDeviceIdentity, identifyOnboarding, isFeaturesFresh, isOnboardingEnabled, isPermissionDue, isUserCancelled, isWireScalar, loadPersistedSession, loadSettledPermissions, looksLikeEmail, lookupIconGlyph, makeSessionId, mergeTheme, mintDeviceId, motionSpec_exports as motionSpec, namespaceExtra, normalizeAfterCard, normalizePermissionStatus, onboardingComponents, parseWireFeatures, peekPersistedSession, permissionEventName, permissionEventProps, permissionScreenId, permissionStorageKey, readCachedFeatures, readProgress, readSettledPermissions, reportClientEvent, reportClientEventAwait, reportClientEvents, reportClientEventsAwait, reportFirstOpen, reportSessionStart, resetActivationRevalidation, resetAutoDeviceKeys, resetCurrentSessionId, resetFirstOpenLatch, resetIdentityProvenance, resetSessionStartGuard, resolveAutoDeviceKey, resolveIdentity, resolvePermissionCopy, resolvePlanTier, resolveUserContext, sanitizeUserId, savePersistedSession, saveSettledPermissions, selectDuePermissionScreen, sessionStorageKey, setCurrentSessionId, subscribeActivationRevalidation, themeFromBrand, toAnalyticsEvent, useActivationRevalidation, useHostIcon, useIllustration, useLifecycleEvents, useOnboardingTheme, useReducedMotion, useResolvedFeatures, useSessionStart, useWireActivation, useWireFeatures, useWireFeaturesContext, wireConfigFromEnv, wireLifecycleEvents, writeCachedFeatures };
|
|
5107
5750
|
//# sourceMappingURL=index.mjs.map
|
|
5108
5751
|
//# sourceMappingURL=index.mjs.map
|