@mushi-mushi/react-native 0.20.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -2
- package/dist/index.cjs +502 -62
- package/dist/index.d.cts +64 -3
- package/dist/index.d.ts +64 -3
- package/dist/index.js +511 -56
- package/package.json +23 -18
- package/CHANGELOG.md +0 -1039
- package/CODE_OF_CONDUCT.md +0 -51
- package/CONTRIBUTING.md +0 -218
- package/SECURITY.md +0 -287
package/dist/index.cjs
CHANGED
|
@@ -49,7 +49,8 @@ module.exports = __toCommonJS(index_exports);
|
|
|
49
49
|
|
|
50
50
|
// src/provider.tsx
|
|
51
51
|
var import_react4 = require("react");
|
|
52
|
-
var
|
|
52
|
+
var import_react_native5 = require("react-native");
|
|
53
|
+
var import_core8 = require("@mushi-mushi/core");
|
|
53
54
|
|
|
54
55
|
// src/capture/console-capture.ts
|
|
55
56
|
var import_core = require("@mushi-mushi/core");
|
|
@@ -543,6 +544,7 @@ var MushiBottomSheet = ({
|
|
|
543
544
|
const [category, setCategory] = (0, import_react.useState)(null);
|
|
544
545
|
const [description, setDescription] = (0, import_react.useState)("");
|
|
545
546
|
const [phase, setPhase] = (0, import_react.useState)("form");
|
|
547
|
+
const [submitError, setSubmitError] = (0, import_react.useState)(null);
|
|
546
548
|
const [sheetTab, setSheetTab] = (0, import_react.useState)("report");
|
|
547
549
|
const [inboxReports, setInboxReports] = (0, import_react.useState)([]);
|
|
548
550
|
const [inboxLoading, setInboxLoading] = (0, import_react.useState)(false);
|
|
@@ -616,6 +618,8 @@ var MushiBottomSheet = ({
|
|
|
616
618
|
...prev,
|
|
617
619
|
{ role: "bot", text: botText, options: reply.options }
|
|
618
620
|
]);
|
|
621
|
+
} catch {
|
|
622
|
+
setAssistantError("Could not reach the assistant. Try again.");
|
|
619
623
|
} finally {
|
|
620
624
|
setAssistantSending(false);
|
|
621
625
|
}
|
|
@@ -689,24 +693,46 @@ var MushiBottomSheet = ({
|
|
|
689
693
|
).current;
|
|
690
694
|
const handleSubmit = async () => {
|
|
691
695
|
if (!category || !description.trim() || !mushi) return;
|
|
696
|
+
const trimmed = description.trim();
|
|
697
|
+
if (trimmed.length < 20) {
|
|
698
|
+
setSubmitError("Please write at least 20 characters so we can understand the issue.");
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
setSubmitError(null);
|
|
692
702
|
setPhase("sending");
|
|
693
703
|
try {
|
|
694
|
-
await mushi.submitReport({
|
|
704
|
+
const outcome = await mushi.submitReport({
|
|
695
705
|
category,
|
|
696
|
-
description:
|
|
706
|
+
description: trimmed,
|
|
697
707
|
screenshotDataUrl: screenshotDataUrl && screenshotAttached ? screenshotDataUrl : void 0
|
|
698
708
|
});
|
|
709
|
+
if (!outcome.ok) {
|
|
710
|
+
if (outcome.failureKind === "credentials" || outcome.failureKind === "quota") {
|
|
711
|
+
setSubmitError("Could not send \u2014 check the project API key or plan quota.");
|
|
712
|
+
setPhase("error");
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (outcome.failureKind === "rate_limited") {
|
|
716
|
+
setSubmitError("Sending too fast \u2014 we queued this and will retry shortly.");
|
|
717
|
+
setPhase("error");
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
setSubmitError("Queued for retry \u2014 we will send it when the connection is back.");
|
|
721
|
+
setPhase("error");
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
699
724
|
setPhase("sent");
|
|
700
725
|
setTimeout(handleClose, 1400);
|
|
701
726
|
} catch {
|
|
702
|
-
|
|
727
|
+
setSubmitError("Something went wrong. Please try again.");
|
|
728
|
+
setPhase("error");
|
|
703
729
|
}
|
|
704
730
|
};
|
|
705
731
|
const activeScreenshot = screenshotDataUrl && screenshotAttached ? screenshotDataUrl : null;
|
|
706
732
|
const pal = (0, import_core3.mushiPalette)(dark ? "dark" : "light");
|
|
707
|
-
const colors = dark ? { bg: pal.paper, text: pal.ink, sub: pal.inkMuted, card: pal.paperRaised, accent: import_core3.MUSHI_BANNER_NEON.bg, accentInk: import_core3.MUSHI_BANNER_NEON.fg, border: pal.ruleStrong, backdrop: "rgba(0,0,0,0.6)", disabled:
|
|
733
|
+
const colors = dark ? { bg: pal.paper, text: pal.ink, sub: pal.inkMuted, card: pal.paperRaised, accent: import_core3.MUSHI_BANNER_NEON.bg, accentInk: import_core3.MUSHI_BANNER_NEON.fg, border: pal.ruleStrong, backdrop: "rgba(0,0,0,0.6)", disabled: import_core3.MUSHI_CONTROL_DISABLED.dark, disabledText: pal.inkFaint } : { bg: pal.paperRaised, text: pal.ink, sub: pal.inkMuted, card: pal.paper, accent: import_core3.MUSHI_BANNER_NEON.bg, accentInk: import_core3.MUSHI_BANNER_NEON.fg, border: pal.ruleStrong, backdrop: "rgba(0,0,0,0.35)", disabled: import_core3.MUSHI_CONTROL_DISABLED.light, disabledText: pal.inkFaint };
|
|
708
734
|
const sheetTabs = ["report", "inbox", ...assistantEnabled ? ["assistant"] : []];
|
|
709
|
-
const canSubmit = !!category && description.trim().length
|
|
735
|
+
const canSubmit = !!category && description.trim().length >= 20 && (phase === "form" || phase === "error");
|
|
710
736
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
711
737
|
import_react_native2.KeyboardAvoidingView,
|
|
712
738
|
{
|
|
@@ -734,7 +760,7 @@ var MushiBottomSheet = ({
|
|
|
734
760
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.brandEyebrow, { color: import_core3.MUSHI_BANNER_NEON.fg }], children: "MUSHI \xB7 BETA" }),
|
|
735
761
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.brandTitle, { color: import_core3.MUSHI_BANNER_NEON.fg }], children: sheetTab === "assistant" ? assistantLabel : sheetTab === "inbox" ? t.flows.reports.title : t.widget.title })
|
|
736
762
|
] }),
|
|
737
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.tabRow, children: sheetTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
763
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [s.tabRow, { borderBottomColor: colors.border }], children: sheetTabs.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
738
764
|
import_react_native2.TouchableOpacity,
|
|
739
765
|
{
|
|
740
766
|
onPress: () => {
|
|
@@ -887,16 +913,22 @@ var MushiBottomSheet = ({
|
|
|
887
913
|
multiline: true,
|
|
888
914
|
textAlignVertical: "top",
|
|
889
915
|
value: description,
|
|
890
|
-
onChangeText:
|
|
891
|
-
|
|
916
|
+
onChangeText: (text) => {
|
|
917
|
+
setDescription(text.slice(0, 4e3));
|
|
918
|
+
if (submitError) setSubmitError(null);
|
|
919
|
+
if (phase === "error") setPhase("form");
|
|
920
|
+
},
|
|
921
|
+
editable: phase === "form" || phase === "error",
|
|
922
|
+
maxLength: 4e3
|
|
892
923
|
}
|
|
893
924
|
),
|
|
925
|
+
submitError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.accent, marginTop: 6, fontSize: 12 }, children: submitError }) : null,
|
|
894
926
|
activeScreenshot ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotRow, children: [
|
|
895
927
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
896
928
|
import_react_native2.Image,
|
|
897
929
|
{
|
|
898
930
|
source: { uri: activeScreenshot },
|
|
899
|
-
style: s.screenshotThumb,
|
|
931
|
+
style: [s.screenshotThumb, { backgroundColor: colors.text }],
|
|
900
932
|
accessibilityLabel: t.step3.screenshotPreviewAlt
|
|
901
933
|
}
|
|
902
934
|
),
|
|
@@ -957,10 +989,10 @@ var s = import_react_native2.StyleSheet.create({
|
|
|
957
989
|
left: 0,
|
|
958
990
|
right: 0,
|
|
959
991
|
minHeight: SHEET_HEIGHT,
|
|
960
|
-
borderTopLeftRadius:
|
|
961
|
-
borderTopRightRadius:
|
|
962
|
-
paddingBottom: import_react_native2.Platform.OS === "ios" ? 34 :
|
|
963
|
-
shadowColor:
|
|
992
|
+
borderTopLeftRadius: import_core3.MUSHI_RADIUS.sheet,
|
|
993
|
+
borderTopRightRadius: import_core3.MUSHI_RADIUS.sheet,
|
|
994
|
+
paddingBottom: import_react_native2.Platform.OS === "ios" ? 34 : import_core3.MUSHI_SPACING.roomy,
|
|
995
|
+
shadowColor: import_core3.MUSHI_SHADOW_INK,
|
|
964
996
|
shadowOffset: { width: 0, height: -3 },
|
|
965
997
|
shadowOpacity: 0.12,
|
|
966
998
|
shadowRadius: 8,
|
|
@@ -968,22 +1000,22 @@ var s = import_react_native2.StyleSheet.create({
|
|
|
968
1000
|
},
|
|
969
1001
|
handleArea: {
|
|
970
1002
|
alignItems: "center",
|
|
971
|
-
paddingVertical:
|
|
1003
|
+
paddingVertical: import_core3.MUSHI_SPACING.comfy
|
|
972
1004
|
},
|
|
973
1005
|
handle: {
|
|
974
1006
|
width: 40,
|
|
975
1007
|
height: 5,
|
|
976
|
-
borderRadius:
|
|
1008
|
+
borderRadius: import_core3.MUSHI_RADIUS.control,
|
|
977
1009
|
opacity: 0.5
|
|
978
1010
|
},
|
|
979
1011
|
brandHeader: {
|
|
980
|
-
paddingHorizontal:
|
|
981
|
-
paddingTop:
|
|
982
|
-
paddingBottom:
|
|
1012
|
+
paddingHorizontal: import_core3.MUSHI_SPACING.lounge,
|
|
1013
|
+
paddingTop: import_core3.MUSHI_SPACING.comfy,
|
|
1014
|
+
paddingBottom: import_core3.MUSHI_SPACING.comfy,
|
|
983
1015
|
borderBottomWidth: 1.5
|
|
984
1016
|
},
|
|
985
1017
|
brandEyebrow: {
|
|
986
|
-
fontSize:
|
|
1018
|
+
fontSize: import_core3.MUSHI_TYPE.sizeLabel,
|
|
987
1019
|
fontWeight: "700",
|
|
988
1020
|
letterSpacing: 1.2,
|
|
989
1021
|
marginBottom: 2,
|
|
@@ -995,59 +1027,61 @@ var s = import_react_native2.StyleSheet.create({
|
|
|
995
1027
|
letterSpacing: -0.2
|
|
996
1028
|
},
|
|
997
1029
|
body: {
|
|
998
|
-
paddingHorizontal:
|
|
999
|
-
paddingTop:
|
|
1030
|
+
paddingHorizontal: import_core3.MUSHI_SPACING.lounge,
|
|
1031
|
+
paddingTop: import_core3.MUSHI_SPACING.tight
|
|
1000
1032
|
},
|
|
1001
1033
|
title: {
|
|
1002
1034
|
fontSize: 20,
|
|
1003
1035
|
fontWeight: "700",
|
|
1004
|
-
marginBottom:
|
|
1036
|
+
marginBottom: import_core3.MUSHI_SPACING.roomy
|
|
1005
1037
|
},
|
|
1006
1038
|
stepLabel: {
|
|
1007
|
-
fontSize:
|
|
1039
|
+
fontSize: import_core3.MUSHI_TYPE.sizeLabel,
|
|
1008
1040
|
fontWeight: "600",
|
|
1009
1041
|
letterSpacing: 0.2,
|
|
1010
|
-
marginTop:
|
|
1011
|
-
marginBottom:
|
|
1042
|
+
marginTop: import_core3.MUSHI_SPACING.snug,
|
|
1043
|
+
marginBottom: import_core3.MUSHI_SPACING.comfy,
|
|
1012
1044
|
textTransform: "uppercase"
|
|
1013
1045
|
},
|
|
1014
1046
|
catRow: {
|
|
1015
1047
|
flexDirection: "row",
|
|
1016
1048
|
justifyContent: "space-between",
|
|
1017
|
-
marginBottom:
|
|
1049
|
+
marginBottom: import_core3.MUSHI_SPACING.roomy
|
|
1018
1050
|
},
|
|
1019
1051
|
catBtn: {
|
|
1020
1052
|
alignItems: "center",
|
|
1021
|
-
paddingVertical:
|
|
1053
|
+
paddingVertical: import_core3.MUSHI_SPACING.comfy,
|
|
1022
1054
|
paddingHorizontal: 6,
|
|
1023
|
-
borderRadius:
|
|
1055
|
+
borderRadius: import_core3.MUSHI_RADIUS.card,
|
|
1024
1056
|
borderWidth: 1,
|
|
1025
1057
|
flex: 1,
|
|
1026
|
-
marginHorizontal: 3
|
|
1058
|
+
marginHorizontal: 3,
|
|
1059
|
+
minHeight: 44
|
|
1027
1060
|
},
|
|
1028
1061
|
catEmoji: {
|
|
1029
1062
|
fontSize: 22,
|
|
1030
|
-
marginBottom:
|
|
1063
|
+
marginBottom: import_core3.MUSHI_SPACING.tight
|
|
1031
1064
|
},
|
|
1032
1065
|
catLabel: {
|
|
1033
|
-
fontSize:
|
|
1066
|
+
fontSize: import_core3.MUSHI_TYPE.sizeLabel,
|
|
1034
1067
|
fontWeight: "600"
|
|
1035
1068
|
},
|
|
1036
1069
|
input: {
|
|
1037
1070
|
borderWidth: 1,
|
|
1038
|
-
borderRadius:
|
|
1071
|
+
borderRadius: import_core3.MUSHI_RADIUS.card,
|
|
1039
1072
|
padding: 14,
|
|
1040
1073
|
fontSize: 15,
|
|
1041
1074
|
minHeight: 100,
|
|
1042
|
-
marginBottom:
|
|
1075
|
+
marginBottom: import_core3.MUSHI_SPACING.roomy
|
|
1043
1076
|
},
|
|
1044
1077
|
submitBtn: {
|
|
1045
|
-
borderRadius:
|
|
1078
|
+
borderRadius: import_core3.MUSHI_RADIUS.card,
|
|
1046
1079
|
paddingVertical: 14,
|
|
1047
|
-
alignItems: "center"
|
|
1080
|
+
alignItems: "center",
|
|
1081
|
+
minHeight: 44
|
|
1048
1082
|
},
|
|
1049
1083
|
submitText: {
|
|
1050
|
-
color:
|
|
1084
|
+
color: import_core3.MUSHI_INVERSE,
|
|
1051
1085
|
fontSize: 16,
|
|
1052
1086
|
fontWeight: "700"
|
|
1053
1087
|
},
|
|
@@ -1078,7 +1112,6 @@ var s = import_react_native2.StyleSheet.create({
|
|
|
1078
1112
|
width: 48,
|
|
1079
1113
|
height: 36,
|
|
1080
1114
|
borderRadius: 4,
|
|
1081
|
-
backgroundColor: "#000",
|
|
1082
1115
|
flexShrink: 0
|
|
1083
1116
|
},
|
|
1084
1117
|
screenshotMeta: {
|
|
@@ -1100,7 +1133,6 @@ var s = import_react_native2.StyleSheet.create({
|
|
|
1100
1133
|
tabRow: {
|
|
1101
1134
|
flexDirection: "row",
|
|
1102
1135
|
borderBottomWidth: import_react_native2.StyleSheet.hairlineWidth,
|
|
1103
|
-
borderBottomColor: "#ccc",
|
|
1104
1136
|
marginBottom: 8
|
|
1105
1137
|
},
|
|
1106
1138
|
tabBtn: {
|
|
@@ -1212,7 +1244,8 @@ var MushiFloatingButton = ({
|
|
|
1212
1244
|
const currentX = (0, import_react2.useRef)(baseX);
|
|
1213
1245
|
const currentY = (0, import_react2.useRef)(0);
|
|
1214
1246
|
const isDragging = (0, import_react2.useRef)(false);
|
|
1215
|
-
const
|
|
1247
|
+
const pal = (0, import_core4.mushiPalette)(dark ? "dark" : "light");
|
|
1248
|
+
const accentColor = accent ?? pal.accent;
|
|
1216
1249
|
(0, import_react2.useEffect)(() => {
|
|
1217
1250
|
if (!persist) return;
|
|
1218
1251
|
void loadStoredPos().then((pos) => {
|
|
@@ -1315,7 +1348,7 @@ var MushiFloatingButton = ({
|
|
|
1315
1348
|
left: posX,
|
|
1316
1349
|
bottom: import_react_native3.Animated.add(new import_react_native3.Animated.Value(baseBottom), import_react_native3.Animated.multiply(posY, -1)),
|
|
1317
1350
|
backgroundColor: accentColor,
|
|
1318
|
-
shadowColor: dark ? accentColor :
|
|
1351
|
+
shadowColor: dark ? accentColor : pal.ink,
|
|
1319
1352
|
transform: [{ scale }]
|
|
1320
1353
|
}
|
|
1321
1354
|
],
|
|
@@ -1404,7 +1437,7 @@ var s3 = import_react_native4.StyleSheet.create({
|
|
|
1404
1437
|
backgroundColor: import_core5.MUSHI_BANNER_NEON.bg,
|
|
1405
1438
|
borderTopWidth: 1,
|
|
1406
1439
|
borderTopColor: import_core5.MUSHI_BANNER_NEON.border,
|
|
1407
|
-
zIndex:
|
|
1440
|
+
zIndex: import_core5.MUSHI_Z.banner,
|
|
1408
1441
|
elevation: 8,
|
|
1409
1442
|
paddingBottom: import_react_native4.Platform.OS === "ios" ? 0 : 0
|
|
1410
1443
|
},
|
|
@@ -1412,12 +1445,13 @@ var s3 = import_react_native4.StyleSheet.create({
|
|
|
1412
1445
|
flex: 1,
|
|
1413
1446
|
flexDirection: "row",
|
|
1414
1447
|
alignItems: "center",
|
|
1415
|
-
gap:
|
|
1416
|
-
paddingHorizontal:
|
|
1417
|
-
paddingVertical:
|
|
1448
|
+
gap: import_core5.MUSHI_SPACING.snug,
|
|
1449
|
+
paddingHorizontal: import_core5.MUSHI_SPACING.comfy,
|
|
1450
|
+
paddingVertical: import_core5.MUSHI_SPACING.snug,
|
|
1451
|
+
minHeight: 44
|
|
1418
1452
|
},
|
|
1419
1453
|
eyebrow: {
|
|
1420
|
-
fontSize:
|
|
1454
|
+
fontSize: import_core5.MUSHI_TYPE.sizeLabel,
|
|
1421
1455
|
fontWeight: "700",
|
|
1422
1456
|
letterSpacing: 1.2,
|
|
1423
1457
|
color: import_core5.MUSHI_BANNER_NEON.fg
|
|
@@ -1446,13 +1480,336 @@ var s3 = import_react_native4.StyleSheet.create({
|
|
|
1446
1480
|
}
|
|
1447
1481
|
});
|
|
1448
1482
|
|
|
1483
|
+
// src/analytics/event-tracker.ts
|
|
1484
|
+
var import_core6 = require("@mushi-mushi/core");
|
|
1485
|
+
var CONSENT_BUFFER_MAX = 50;
|
|
1486
|
+
var SPILL_MAX = 200;
|
|
1487
|
+
var SPILL_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1488
|
+
var SURFACE = "mobile";
|
|
1489
|
+
async function defaultStorage() {
|
|
1490
|
+
try {
|
|
1491
|
+
const mod = await import("@react-native-async-storage/async-storage");
|
|
1492
|
+
return mod.default;
|
|
1493
|
+
} catch {
|
|
1494
|
+
return null;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
function createRNEventTracker(opts) {
|
|
1498
|
+
const cfg = opts.config ?? {};
|
|
1499
|
+
const enabled = cfg.enabled !== false;
|
|
1500
|
+
const consentMode = cfg.consent ?? "implied";
|
|
1501
|
+
const allowlist = cfg.propertyAllowlist ?? [];
|
|
1502
|
+
const spillKey = `@mushi:events_spill_${opts.projectId}`;
|
|
1503
|
+
const consentKey = `@mushi:analytics_consent_${opts.projectId}`;
|
|
1504
|
+
let buffer = [];
|
|
1505
|
+
let consentBuffer = [];
|
|
1506
|
+
let consent = consentMode === "required" ? "pending" : "granted";
|
|
1507
|
+
let userId = null;
|
|
1508
|
+
let userTraits = null;
|
|
1509
|
+
let pendingIdentify = false;
|
|
1510
|
+
let flushing = false;
|
|
1511
|
+
let destroyed = false;
|
|
1512
|
+
let timer = null;
|
|
1513
|
+
let storage = null;
|
|
1514
|
+
const consentListeners = /* @__PURE__ */ new Set();
|
|
1515
|
+
const loadStorage = opts.storage ?? defaultStorage;
|
|
1516
|
+
const resolveClient = () => typeof opts.client === "function" ? opts.client() : opts.client;
|
|
1517
|
+
async function readSpill() {
|
|
1518
|
+
if (!storage) return [];
|
|
1519
|
+
try {
|
|
1520
|
+
const raw = await storage.getItem(spillKey);
|
|
1521
|
+
if (!raw) return [];
|
|
1522
|
+
const parsed = JSON.parse(raw);
|
|
1523
|
+
if (!parsed || Date.now() - parsed.at > SPILL_TTL_MS) {
|
|
1524
|
+
await storage.removeItem(spillKey);
|
|
1525
|
+
return [];
|
|
1526
|
+
}
|
|
1527
|
+
return Array.isArray(parsed.events) ? parsed.events.slice(0, SPILL_MAX) : [];
|
|
1528
|
+
} catch {
|
|
1529
|
+
return [];
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
async function writeSpill(events) {
|
|
1533
|
+
if (!storage) return;
|
|
1534
|
+
try {
|
|
1535
|
+
if (events.length === 0) await storage.removeItem(spillKey);
|
|
1536
|
+
else await storage.setItem(spillKey, JSON.stringify({ at: Date.now(), events: events.slice(-SPILL_MAX) }));
|
|
1537
|
+
} catch {
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
async function flushNow() {
|
|
1541
|
+
if (destroyed && buffer.length === 0) return;
|
|
1542
|
+
await ready;
|
|
1543
|
+
if (flushing || consent !== "granted") return;
|
|
1544
|
+
if (buffer.length === 0 && !pendingIdentify) return;
|
|
1545
|
+
const client = resolveClient();
|
|
1546
|
+
if (!client) return;
|
|
1547
|
+
flushing = true;
|
|
1548
|
+
const batch = buffer.splice(0, import_core6.EVENT_PROPERTY_LIMITS.maxServerBatch);
|
|
1549
|
+
if (pendingIdentify) {
|
|
1550
|
+
batch.unshift({ name: "identify", ts: (/* @__PURE__ */ new Date()).toISOString(), properties: {} });
|
|
1551
|
+
pendingIdentify = false;
|
|
1552
|
+
}
|
|
1553
|
+
try {
|
|
1554
|
+
const anonId = await Promise.resolve(opts.getAnonId());
|
|
1555
|
+
const payload = {
|
|
1556
|
+
anon_id: anonId,
|
|
1557
|
+
user_id: userId ?? opts.getUserId?.() ?? null,
|
|
1558
|
+
user_traits: userTraits ?? opts.getUserTraits?.() ?? null,
|
|
1559
|
+
session_id: null,
|
|
1560
|
+
sdk_version: opts.sdkVersion ?? null,
|
|
1561
|
+
surface: SURFACE,
|
|
1562
|
+
events: batch
|
|
1563
|
+
};
|
|
1564
|
+
const res = await client.postProductEvents(payload);
|
|
1565
|
+
if (!res.ok) await writeSpill([...await readSpill(), ...batch.filter((e) => e.name !== "identify")]);
|
|
1566
|
+
else await writeSpill([]);
|
|
1567
|
+
} catch {
|
|
1568
|
+
await writeSpill([...await readSpill(), ...batch.filter((e) => e.name !== "identify")]);
|
|
1569
|
+
} finally {
|
|
1570
|
+
flushing = false;
|
|
1571
|
+
}
|
|
1572
|
+
if (buffer.length >= import_core6.EVENT_PROPERTY_LIMITS.maxClientBatch) void flushNow();
|
|
1573
|
+
}
|
|
1574
|
+
function enqueue(ev) {
|
|
1575
|
+
if (consent === "denied") return;
|
|
1576
|
+
if (consent === "pending") {
|
|
1577
|
+
if (consentBuffer.length < CONSENT_BUFFER_MAX) consentBuffer.push(ev);
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
buffer.push(ev);
|
|
1581
|
+
if (buffer.length >= import_core6.EVENT_PROPERTY_LIMITS.maxClientBatch) void flushNow();
|
|
1582
|
+
}
|
|
1583
|
+
const ready = enabled ? (async () => {
|
|
1584
|
+
storage = await loadStorage();
|
|
1585
|
+
if (destroyed) return;
|
|
1586
|
+
let stored = null;
|
|
1587
|
+
try {
|
|
1588
|
+
stored = storage ? await storage.getItem(consentKey) : null;
|
|
1589
|
+
} catch {
|
|
1590
|
+
stored = null;
|
|
1591
|
+
}
|
|
1592
|
+
if (stored === "denied") {
|
|
1593
|
+
consent = "denied";
|
|
1594
|
+
buffer = [];
|
|
1595
|
+
consentBuffer = [];
|
|
1596
|
+
pendingIdentify = false;
|
|
1597
|
+
await writeSpill([]);
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
if (stored === "granted" && consent === "pending") {
|
|
1601
|
+
consent = "granted";
|
|
1602
|
+
buffer.push(...consentBuffer);
|
|
1603
|
+
consentBuffer = [];
|
|
1604
|
+
}
|
|
1605
|
+
const spill = await readSpill();
|
|
1606
|
+
if (spill.length > 0) {
|
|
1607
|
+
buffer.unshift(...spill);
|
|
1608
|
+
await writeSpill([]);
|
|
1609
|
+
}
|
|
1610
|
+
})().catch(() => void 0) : Promise.resolve();
|
|
1611
|
+
if (enabled) {
|
|
1612
|
+
const interval = Math.max(1e3, cfg.flushIntervalMs ?? 5e3);
|
|
1613
|
+
timer = setInterval(() => {
|
|
1614
|
+
void flushNow();
|
|
1615
|
+
}, interval);
|
|
1616
|
+
}
|
|
1617
|
+
return {
|
|
1618
|
+
ready,
|
|
1619
|
+
track(name, properties, trackOpts) {
|
|
1620
|
+
if (!enabled || destroyed) return false;
|
|
1621
|
+
if (!(0, import_core6.isValidEventName)(name)) return false;
|
|
1622
|
+
const { properties: props } = (0, import_core6.sanitizeEventProperties)(properties, { allowlist, scrub: opts.scrub });
|
|
1623
|
+
let merged = props;
|
|
1624
|
+
if (trackOpts?.reserved) {
|
|
1625
|
+
const { properties: res } = (0, import_core6.sanitizeEventProperties)(trackOpts.reserved, { allowReserved: true, scrub: opts.scrub });
|
|
1626
|
+
merged = { ...props, ...res };
|
|
1627
|
+
}
|
|
1628
|
+
merged.$surface = SURFACE;
|
|
1629
|
+
const route = opts.getRoute?.();
|
|
1630
|
+
if (route && merged.$route === void 0) merged.$route = route;
|
|
1631
|
+
if (!(0, import_core6.propertiesWithinByteLimit)(merged)) return false;
|
|
1632
|
+
enqueue({
|
|
1633
|
+
name,
|
|
1634
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1635
|
+
properties: merged,
|
|
1636
|
+
...trackOpts?.dedupKey ? { dedup_key: trackOpts.dedupKey } : {}
|
|
1637
|
+
});
|
|
1638
|
+
return true;
|
|
1639
|
+
},
|
|
1640
|
+
setConsent(state) {
|
|
1641
|
+
void ready.then(() => storage?.setItem(consentKey, state)).catch(() => void 0);
|
|
1642
|
+
if (state === "denied") {
|
|
1643
|
+
consent = "denied";
|
|
1644
|
+
buffer = [];
|
|
1645
|
+
consentBuffer = [];
|
|
1646
|
+
pendingIdentify = false;
|
|
1647
|
+
void writeSpill([]);
|
|
1648
|
+
} else {
|
|
1649
|
+
const wasPending = consent === "pending";
|
|
1650
|
+
consent = "granted";
|
|
1651
|
+
if (wasPending && consentBuffer.length > 0) {
|
|
1652
|
+
buffer.push(...consentBuffer);
|
|
1653
|
+
consentBuffer = [];
|
|
1654
|
+
}
|
|
1655
|
+
if (buffer.length > 0 || pendingIdentify) void flushNow();
|
|
1656
|
+
}
|
|
1657
|
+
for (const listener of [...consentListeners]) {
|
|
1658
|
+
try {
|
|
1659
|
+
listener(state);
|
|
1660
|
+
} catch {
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
},
|
|
1664
|
+
setIdentity(id, traits) {
|
|
1665
|
+
userId = id;
|
|
1666
|
+
userTraits = traits ?? null;
|
|
1667
|
+
if (id && enabled && consent !== "denied") {
|
|
1668
|
+
pendingIdentify = true;
|
|
1669
|
+
void flushNow();
|
|
1670
|
+
}
|
|
1671
|
+
},
|
|
1672
|
+
consentState() {
|
|
1673
|
+
return enabled ? consent : "denied";
|
|
1674
|
+
},
|
|
1675
|
+
onConsentChange(listener) {
|
|
1676
|
+
consentListeners.add(listener);
|
|
1677
|
+
return () => {
|
|
1678
|
+
consentListeners.delete(listener);
|
|
1679
|
+
};
|
|
1680
|
+
},
|
|
1681
|
+
async flush() {
|
|
1682
|
+
await ready;
|
|
1683
|
+
await flushNow();
|
|
1684
|
+
},
|
|
1685
|
+
async destroy() {
|
|
1686
|
+
if (timer != null) {
|
|
1687
|
+
clearInterval(timer);
|
|
1688
|
+
timer = null;
|
|
1689
|
+
}
|
|
1690
|
+
await flushNow().catch(() => void 0);
|
|
1691
|
+
destroyed = true;
|
|
1692
|
+
buffer = [];
|
|
1693
|
+
consentBuffer = [];
|
|
1694
|
+
pendingIdentify = false;
|
|
1695
|
+
}
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
// src/analytics/session-tracker.ts
|
|
1700
|
+
var import_core7 = require("@mushi-mushi/core");
|
|
1701
|
+
var HEARTBEAT_INTERVAL_MS = 6e4;
|
|
1702
|
+
var RESUME_WINDOW_MS = 30 * 6e4;
|
|
1703
|
+
function createRNSessionTracker(opts) {
|
|
1704
|
+
const clock = opts.now ?? Date.now;
|
|
1705
|
+
const resolveClient = () => typeof opts.client === "function" ? opts.client() : opts.client;
|
|
1706
|
+
let sessionId = opts.sessionId ?? `sess_${(0, import_core7.newUuid)()}`;
|
|
1707
|
+
let pageViewCount = 0;
|
|
1708
|
+
let started = false;
|
|
1709
|
+
let allowed = false;
|
|
1710
|
+
let foreground = true;
|
|
1711
|
+
let destroyed = false;
|
|
1712
|
+
let backgroundedAt = null;
|
|
1713
|
+
let heartbeat = null;
|
|
1714
|
+
function route() {
|
|
1715
|
+
return opts.getRoute?.() ?? null;
|
|
1716
|
+
}
|
|
1717
|
+
function send(kind, extra) {
|
|
1718
|
+
const client = resolveClient();
|
|
1719
|
+
if (!client || !allowed || destroyed) return;
|
|
1720
|
+
const base = {
|
|
1721
|
+
kind,
|
|
1722
|
+
session_id: sessionId,
|
|
1723
|
+
ts: new Date(clock()).toISOString(),
|
|
1724
|
+
page_view_count: pageViewCount,
|
|
1725
|
+
user_agent: opts.userAgent ?? null,
|
|
1726
|
+
sdk_version: opts.sdkVersion
|
|
1727
|
+
};
|
|
1728
|
+
void Promise.resolve(opts.getReporterToken()).then((token) => client.postSessionEvent({ ...base, reporter_token_hash: token, ...extra })).catch(() => void 0);
|
|
1729
|
+
}
|
|
1730
|
+
function stopHeartbeat() {
|
|
1731
|
+
if (heartbeat != null) {
|
|
1732
|
+
clearInterval(heartbeat);
|
|
1733
|
+
heartbeat = null;
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
function startHeartbeat() {
|
|
1737
|
+
stopHeartbeat();
|
|
1738
|
+
heartbeat = setInterval(() => send("session_heartbeat", { route: route() }), HEARTBEAT_INTERVAL_MS);
|
|
1739
|
+
}
|
|
1740
|
+
function startSession() {
|
|
1741
|
+
started = true;
|
|
1742
|
+
pageViewCount = 1;
|
|
1743
|
+
send("session_start", { route: route() });
|
|
1744
|
+
startHeartbeat();
|
|
1745
|
+
}
|
|
1746
|
+
function activate() {
|
|
1747
|
+
if (allowed || destroyed) return;
|
|
1748
|
+
allowed = true;
|
|
1749
|
+
if (!foreground) return;
|
|
1750
|
+
if (!started) startSession();
|
|
1751
|
+
else startHeartbeat();
|
|
1752
|
+
}
|
|
1753
|
+
function deactivate() {
|
|
1754
|
+
allowed = false;
|
|
1755
|
+
stopHeartbeat();
|
|
1756
|
+
}
|
|
1757
|
+
const unsubscribe = opts.consent.onConsentChange(() => {
|
|
1758
|
+
if (opts.consent.consentState() === "granted") activate();
|
|
1759
|
+
else deactivate();
|
|
1760
|
+
});
|
|
1761
|
+
void opts.consent.ready.then(() => {
|
|
1762
|
+
if (opts.consent.consentState() === "granted") activate();
|
|
1763
|
+
}).catch(() => void 0);
|
|
1764
|
+
return {
|
|
1765
|
+
onAppStateChange(state) {
|
|
1766
|
+
if (destroyed) return;
|
|
1767
|
+
if (state === "active") {
|
|
1768
|
+
if (foreground) return;
|
|
1769
|
+
foreground = true;
|
|
1770
|
+
const away = backgroundedAt == null ? 0 : clock() - backgroundedAt;
|
|
1771
|
+
backgroundedAt = null;
|
|
1772
|
+
if (!allowed) return;
|
|
1773
|
+
if (!started || away >= RESUME_WINDOW_MS) {
|
|
1774
|
+
sessionId = `sess_${(0, import_core7.newUuid)()}`;
|
|
1775
|
+
startSession();
|
|
1776
|
+
} else {
|
|
1777
|
+
send("session_heartbeat", { route: route() });
|
|
1778
|
+
startHeartbeat();
|
|
1779
|
+
}
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
if (state !== "background" || !foreground) return;
|
|
1783
|
+
foreground = false;
|
|
1784
|
+
backgroundedAt = clock();
|
|
1785
|
+
stopHeartbeat();
|
|
1786
|
+
if (started) send("session_end", { route: route() });
|
|
1787
|
+
},
|
|
1788
|
+
pageView(next) {
|
|
1789
|
+
if (!allowed || !started || !foreground) return;
|
|
1790
|
+
pageViewCount += 1;
|
|
1791
|
+
send("page_view", { route: next });
|
|
1792
|
+
},
|
|
1793
|
+
destroy() {
|
|
1794
|
+
destroyed = true;
|
|
1795
|
+
deactivate();
|
|
1796
|
+
unsubscribe();
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1449
1801
|
// src/version.ts
|
|
1450
1802
|
var MUSHI_SDK_PACKAGE = "@mushi-mushi/react-native";
|
|
1451
|
-
var MUSHI_SDK_VERSION = "0.
|
|
1803
|
+
var MUSHI_SDK_VERSION = "0.22.0" ? "0.22.0" : "0.17.0";
|
|
1452
1804
|
|
|
1453
1805
|
// src/provider.tsx
|
|
1454
1806
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1455
1807
|
var MushiContext = (0, import_react4.createContext)(null);
|
|
1808
|
+
function nativeUserAgent() {
|
|
1809
|
+
const os = import_react_native5.Platform?.OS ?? "native";
|
|
1810
|
+
const version = import_react_native5.Platform?.Version;
|
|
1811
|
+
return `${MUSHI_SDK_PACKAGE}/${MUSHI_SDK_VERSION} (${version != null ? `${os} ${String(version)}` : os})`;
|
|
1812
|
+
}
|
|
1456
1813
|
var DEFAULT_SCREENSHOT_HINT = "Check the preview \u2014 remove it if any private info (balances, personal details) is visible.";
|
|
1457
1814
|
function resolveScreenshotHint(v) {
|
|
1458
1815
|
if (v === false) return null;
|
|
@@ -1495,9 +1852,9 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1495
1852
|
const networkRef = (0, import_react4.useRef)(null);
|
|
1496
1853
|
const queueRef = (0, import_react4.useRef)(null);
|
|
1497
1854
|
const apiClientRef = (0, import_react4.useRef)(null);
|
|
1498
|
-
const sessionIdRef = (0, import_react4.useRef)(`sess_${(0,
|
|
1855
|
+
const sessionIdRef = (0, import_react4.useRef)(`sess_${(0, import_core8.newUuid)()}`);
|
|
1499
1856
|
const breadcrumbsRef = (0, import_react4.useRef)(
|
|
1500
|
-
(0,
|
|
1857
|
+
(0, import_core8.createBreadcrumbBuffer)({ max: config.capture?.maxBreadcrumbs ?? 50 })
|
|
1501
1858
|
);
|
|
1502
1859
|
const [sheetVisible, setSheetVisible] = (0, import_react4.useState)(false);
|
|
1503
1860
|
const [sheetPreferredTab, setSheetPreferredTab] = (0, import_react4.useState)("report");
|
|
@@ -1522,7 +1879,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1522
1879
|
if (config.endpoint && !config.apiEndpoint && __DEV__) {
|
|
1523
1880
|
console.warn("[MushiProvider] `endpoint` is deprecated \u2014 use `apiEndpoint` instead.");
|
|
1524
1881
|
}
|
|
1525
|
-
const apiEndpoint = rawEndpoint ??
|
|
1882
|
+
const apiEndpoint = rawEndpoint ?? import_core8.DEFAULT_API_ENDPOINT;
|
|
1526
1883
|
(0, import_react4.useEffect)(() => {
|
|
1527
1884
|
if (config.capture?.console !== false) {
|
|
1528
1885
|
consoleRef.current = setupConsoleCapture(config.capture?.maxConsoleEntries);
|
|
@@ -1530,7 +1887,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1530
1887
|
if (config.capture?.network !== false) {
|
|
1531
1888
|
networkRef.current = setupNetworkCapture(config.capture?.maxNetworkEntries, apiEndpoint);
|
|
1532
1889
|
}
|
|
1533
|
-
apiClientRef.current = (0,
|
|
1890
|
+
apiClientRef.current = (0, import_core8.createApiClient)({
|
|
1534
1891
|
projectId: config.projectId,
|
|
1535
1892
|
apiKey: config.apiKey,
|
|
1536
1893
|
apiEndpoint,
|
|
@@ -1567,14 +1924,14 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1567
1924
|
if (existing && existing.startsWith("mushi_")) {
|
|
1568
1925
|
reporterTokenRef.current = existing;
|
|
1569
1926
|
} else {
|
|
1570
|
-
const fresh = `mushi_${(0,
|
|
1927
|
+
const fresh = `mushi_${(0, import_core8.newUuid)()}`;
|
|
1571
1928
|
reporterTokenRef.current = fresh;
|
|
1572
1929
|
await saveReporterToken(fresh, secureStorage);
|
|
1573
1930
|
}
|
|
1574
1931
|
resolveReporterTokenReady();
|
|
1575
1932
|
} catch {
|
|
1576
1933
|
if (reporterTokenRef.current === `rn-${config.projectId}-anon`) {
|
|
1577
|
-
reporterTokenRef.current = `mushi_${(0,
|
|
1934
|
+
reporterTokenRef.current = `mushi_${(0, import_core8.newUuid)()}`;
|
|
1578
1935
|
}
|
|
1579
1936
|
resolveReporterTokenReady();
|
|
1580
1937
|
}
|
|
@@ -1694,7 +2051,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1694
2051
|
const merged = Object.keys(metadataRef.current).length > 0 ? { ...contextMeta, ...metadataRef.current } : Object.keys(contextMeta).length > 0 ? contextMeta : void 0;
|
|
1695
2052
|
const breadcrumbs = breadcrumbsRef.current.getAll().map((c) => ({
|
|
1696
2053
|
...c,
|
|
1697
|
-
message: (0,
|
|
2054
|
+
message: (0, import_core8.scrubPii)(c.message)
|
|
1698
2055
|
}));
|
|
1699
2056
|
const timeline = breadcrumbs.map((c) => ({
|
|
1700
2057
|
ts: c.timestamp,
|
|
@@ -1703,10 +2060,10 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1703
2060
|
}));
|
|
1704
2061
|
const fingerprintHash = getDeviceFingerprintHash(deviceInfo);
|
|
1705
2062
|
const report = {
|
|
1706
|
-
id: (0,
|
|
2063
|
+
id: (0, import_core8.newUuid)(),
|
|
1707
2064
|
projectId: config.projectId,
|
|
1708
2065
|
category: data.category,
|
|
1709
|
-
description: (0,
|
|
2066
|
+
description: (0, import_core8.scrubPii)(data.description),
|
|
1710
2067
|
environment: {
|
|
1711
2068
|
userAgent: deviceInfo.systemName ?? "ReactNative",
|
|
1712
2069
|
platform: deviceInfo.platform ?? "mobile",
|
|
@@ -1719,7 +2076,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1719
2076
|
},
|
|
1720
2077
|
consoleLogs: (consoleRef.current?.getEntries() ?? []).map((entry) => ({
|
|
1721
2078
|
...entry,
|
|
1722
|
-
message: (0,
|
|
2079
|
+
message: (0, import_core8.scrubPii)(entry.message)
|
|
1723
2080
|
})),
|
|
1724
2081
|
networkLogs: networkRef.current?.getEntries().map((entry) => ({
|
|
1725
2082
|
...entry,
|
|
@@ -1738,21 +2095,34 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1738
2095
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1739
2096
|
};
|
|
1740
2097
|
const client = apiClientRef.current;
|
|
1741
|
-
if (!client)
|
|
2098
|
+
if (!client) {
|
|
2099
|
+
console.error(
|
|
2100
|
+
"[Mushi] submitReport called before the SDK finished initializing. Ensure <MushiProvider> is mounted with a valid projectId and apiKey."
|
|
2101
|
+
);
|
|
2102
|
+
return { ok: false, failureKind: "permanent" };
|
|
2103
|
+
}
|
|
1742
2104
|
const result = await client.submitReport(report);
|
|
1743
2105
|
if (!result.ok) {
|
|
1744
|
-
const
|
|
2106
|
+
const code = result.error?.code ?? "";
|
|
2107
|
+
const isCredentialError = code === "HTTP_401" || code === "HTTP_403" || code.includes("UNAUTHORIZED") || code.includes("FORBIDDEN") || code.includes("INVALID_API_KEY");
|
|
1745
2108
|
if (isCredentialError) {
|
|
1746
|
-
const where = apiEndpoint.replace(/\/$/, "") ===
|
|
2109
|
+
const where = apiEndpoint.replace(/\/$/, "") === import_core8.DEFAULT_API_ENDPOINT ? "Get the correct values at: https://kensaur.us/mushi-mushi/admin/projects" : "Get the correct values from your Mushi console's Projects page.";
|
|
1747
2110
|
console.error(
|
|
1748
2111
|
'[Mushi] Credentials rejected. Check your projectId and apiKey (must have "report:write" scope). ' + where
|
|
1749
2112
|
);
|
|
1750
|
-
return;
|
|
2113
|
+
return { ok: false, failureKind: "credentials" };
|
|
1751
2114
|
}
|
|
2115
|
+
const isRateLimited = code === "HTTP_429" || code.includes("RATE_LIMIT");
|
|
1752
2116
|
await queueRef.current?.enqueue(report);
|
|
1753
2117
|
config.callbacks?.onReportQueued?.(report.id);
|
|
2118
|
+
return {
|
|
2119
|
+
ok: false,
|
|
2120
|
+
queued: true,
|
|
2121
|
+
failureKind: isRateLimited ? "rate_limited" : "retrying"
|
|
2122
|
+
};
|
|
1754
2123
|
} else {
|
|
1755
2124
|
config.callbacks?.onReportSubmitted?.(report.id);
|
|
2125
|
+
return { ok: true };
|
|
1756
2126
|
}
|
|
1757
2127
|
},
|
|
1758
2128
|
[config.projectId, config.callbacks, apiEndpoint]
|
|
@@ -1782,6 +2152,63 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1782
2152
|
if (rewardsFlushRef.current) clearInterval(rewardsFlushRef.current);
|
|
1783
2153
|
};
|
|
1784
2154
|
}, [config.rewards?.enabled, config.rewards?.trackActivity, config.rewards?.flushIntervalMs]);
|
|
2155
|
+
const eventTrackerRef = (0, import_react4.useRef)(null);
|
|
2156
|
+
const sessionTrackerRef = (0, import_react4.useRef)(null);
|
|
2157
|
+
(0, import_react4.useEffect)(() => {
|
|
2158
|
+
const getRoute = () => screenRef.current?.route ?? screenRef.current?.name ?? null;
|
|
2159
|
+
const tracker = createRNEventTracker({
|
|
2160
|
+
projectId: config.projectId,
|
|
2161
|
+
client: () => apiClientRef.current,
|
|
2162
|
+
getAnonId: async () => {
|
|
2163
|
+
await reporterTokenReadyRef.current;
|
|
2164
|
+
return reporterTokenRef.current;
|
|
2165
|
+
},
|
|
2166
|
+
getUserId: () => userRef.current?.id ?? null,
|
|
2167
|
+
getRoute,
|
|
2168
|
+
sdkVersion: MUSHI_SDK_VERSION,
|
|
2169
|
+
config: config.analytics,
|
|
2170
|
+
scrub: import_core8.scrubPii
|
|
2171
|
+
});
|
|
2172
|
+
eventTrackerRef.current = tracker;
|
|
2173
|
+
const sessions = config.trackSessions === false ? null : createRNSessionTracker({
|
|
2174
|
+
client: () => apiClientRef.current,
|
|
2175
|
+
getReporterToken: async () => {
|
|
2176
|
+
await reporterTokenReadyRef.current;
|
|
2177
|
+
return reporterTokenRef.current;
|
|
2178
|
+
},
|
|
2179
|
+
getRoute,
|
|
2180
|
+
consent: tracker,
|
|
2181
|
+
sessionId: sessionIdRef.current,
|
|
2182
|
+
sdkVersion: MUSHI_SDK_VERSION,
|
|
2183
|
+
userAgent: nativeUserAgent()
|
|
2184
|
+
});
|
|
2185
|
+
sessionTrackerRef.current = sessions;
|
|
2186
|
+
let sub;
|
|
2187
|
+
try {
|
|
2188
|
+
if (typeof import_react_native5.AppState?.addEventListener === "function") {
|
|
2189
|
+
sub = import_react_native5.AppState.addEventListener("change", (state) => {
|
|
2190
|
+
sessions?.onAppStateChange(state);
|
|
2191
|
+
if (state !== "active") tracker.flush().catch(() => {
|
|
2192
|
+
});
|
|
2193
|
+
});
|
|
2194
|
+
}
|
|
2195
|
+
} catch {
|
|
2196
|
+
}
|
|
2197
|
+
return () => {
|
|
2198
|
+
sub?.remove();
|
|
2199
|
+
sessions?.destroy();
|
|
2200
|
+
sessionTrackerRef.current = null;
|
|
2201
|
+
eventTrackerRef.current = null;
|
|
2202
|
+
tracker.destroy().catch(() => {
|
|
2203
|
+
});
|
|
2204
|
+
};
|
|
2205
|
+
}, [
|
|
2206
|
+
config.projectId,
|
|
2207
|
+
config.analytics?.enabled,
|
|
2208
|
+
config.analytics?.consent,
|
|
2209
|
+
config.analytics?.flushIntervalMs,
|
|
2210
|
+
config.trackSessions
|
|
2211
|
+
]);
|
|
1785
2212
|
const instance = (0, import_react4.useMemo)(
|
|
1786
2213
|
() => ({
|
|
1787
2214
|
open,
|
|
@@ -1799,6 +2226,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1799
2226
|
name: traits?.name,
|
|
1800
2227
|
provider: traits?.provider
|
|
1801
2228
|
};
|
|
2229
|
+
eventTrackerRef.current?.setIdentity(userId, traits ?? null);
|
|
1802
2230
|
},
|
|
1803
2231
|
setUser(user) {
|
|
1804
2232
|
userRef.current = {
|
|
@@ -1807,18 +2235,22 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1807
2235
|
name: user.name,
|
|
1808
2236
|
provider: user.provider
|
|
1809
2237
|
};
|
|
2238
|
+
eventTrackerRef.current?.setIdentity(user.id, { email: user.email, name: user.name, provider: user.provider });
|
|
1810
2239
|
},
|
|
1811
2240
|
identifyWithToken(token) {
|
|
1812
2241
|
userTokenRef.current = token && typeof token === "string" ? token : null;
|
|
1813
2242
|
if (userTokenRef.current) {
|
|
1814
|
-
const claims = (0,
|
|
2243
|
+
const claims = (0, import_core8.parseIdentityToken)(userTokenRef.current);
|
|
1815
2244
|
if (claims?.sub) {
|
|
1816
2245
|
userRef.current = {
|
|
1817
2246
|
id: claims.sub,
|
|
1818
2247
|
...claims.email ? { email: claims.email } : {},
|
|
1819
2248
|
...claims.name ? { name: claims.name } : {}
|
|
1820
2249
|
};
|
|
2250
|
+
eventTrackerRef.current?.setIdentity(claims.sub, null);
|
|
1821
2251
|
}
|
|
2252
|
+
} else {
|
|
2253
|
+
eventTrackerRef.current?.setIdentity(null, null);
|
|
1822
2254
|
}
|
|
1823
2255
|
},
|
|
1824
2256
|
publishPageContext(context) {
|
|
@@ -1831,6 +2263,7 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1831
2263
|
const prev = screenRef.current?.name;
|
|
1832
2264
|
screenRef.current = screen;
|
|
1833
2265
|
if (screen.name && screen.name !== prev) {
|
|
2266
|
+
sessionTrackerRef.current?.pageView(screen.route ?? screen.name);
|
|
1834
2267
|
breadcrumbsRef.current.add({
|
|
1835
2268
|
category: "navigation",
|
|
1836
2269
|
level: "info",
|
|
@@ -1892,6 +2325,13 @@ function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
|
1892
2325
|
if (!config.rewards?.enabled) return;
|
|
1893
2326
|
activityQueueRef.current.push({ action, metadata });
|
|
1894
2327
|
},
|
|
2328
|
+
// Product analytics (Users & Funnels)
|
|
2329
|
+
track(event, properties) {
|
|
2330
|
+
return eventTrackerRef.current?.track(event, properties) ?? false;
|
|
2331
|
+
},
|
|
2332
|
+
setConsent(state) {
|
|
2333
|
+
eventTrackerRef.current?.setConsent(state);
|
|
2334
|
+
},
|
|
1895
2335
|
async getTier() {
|
|
1896
2336
|
const userId = userRef.current?.id;
|
|
1897
2337
|
const client = apiClientRef.current;
|