@mushi-mushi/react-native 0.15.2 → 0.17.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/CHANGELOG.md +932 -0
- package/CONTRIBUTING.md +50 -9
- package/README.md +37 -2
- package/dist/index.cjs +461 -130
- package/dist/index.d.cts +59 -22
- package/dist/index.d.ts +59 -22
- package/dist/index.js +469 -134
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
9
9
|
import {
|
|
10
10
|
createContext,
|
|
11
11
|
useContext,
|
|
12
|
-
useEffect as
|
|
12
|
+
useEffect as useEffect3,
|
|
13
13
|
useRef as useRef3,
|
|
14
14
|
useState as useState2,
|
|
15
15
|
useCallback as useCallback3,
|
|
@@ -17,8 +17,10 @@ import {
|
|
|
17
17
|
} from "react";
|
|
18
18
|
import {
|
|
19
19
|
createApiClient,
|
|
20
|
+
createBreadcrumbBuffer,
|
|
20
21
|
DEFAULT_API_ENDPOINT,
|
|
21
|
-
newUuid
|
|
22
|
+
newUuid,
|
|
23
|
+
parseIdentityToken
|
|
22
24
|
} from "@mushi-mushi/core";
|
|
23
25
|
|
|
24
26
|
// src/capture/console-capture.ts
|
|
@@ -136,6 +138,30 @@ function getDeviceInfo() {
|
|
|
136
138
|
};
|
|
137
139
|
}
|
|
138
140
|
|
|
141
|
+
// src/capture/fingerprint.ts
|
|
142
|
+
function nonCryptoHashHex(input) {
|
|
143
|
+
let hash = 0;
|
|
144
|
+
for (let i = 0; i < input.length; i++) {
|
|
145
|
+
hash = (hash << 5) - hash + input.charCodeAt(i);
|
|
146
|
+
hash |= 0;
|
|
147
|
+
}
|
|
148
|
+
return `rnfp_${(hash >>> 0).toString(16).padStart(8, "0")}`;
|
|
149
|
+
}
|
|
150
|
+
function getDeviceFingerprintHash(device) {
|
|
151
|
+
const inputs = {
|
|
152
|
+
platform: device.platform,
|
|
153
|
+
osVersion: String(device.osVersion ?? ""),
|
|
154
|
+
screenWidth: device.screenWidth,
|
|
155
|
+
screenHeight: device.screenHeight,
|
|
156
|
+
pixelRatio: device.pixelRatio,
|
|
157
|
+
locale: device.locale ?? "",
|
|
158
|
+
timezone: device.timezone ?? ""
|
|
159
|
+
};
|
|
160
|
+
const serialised = JSON.stringify(inputs);
|
|
161
|
+
if (!device.platform && !device.screenWidth) return null;
|
|
162
|
+
return nonCryptoHashHex(serialised);
|
|
163
|
+
}
|
|
164
|
+
|
|
139
165
|
// src/storage/async-storage-queue.ts
|
|
140
166
|
var QUEUE_KEY = "@mushi:offline_queue";
|
|
141
167
|
var AsyncStorageQueue = class {
|
|
@@ -306,12 +332,20 @@ import {
|
|
|
306
332
|
Dimensions as Dimensions2,
|
|
307
333
|
Platform as Platform2,
|
|
308
334
|
KeyboardAvoidingView,
|
|
335
|
+
ScrollView,
|
|
309
336
|
useColorScheme
|
|
310
337
|
} from "react-native";
|
|
338
|
+
import { mushiPalette } from "@mushi-mushi/core";
|
|
311
339
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
312
340
|
var { height: SCREEN_HEIGHT } = Dimensions2.get("window");
|
|
313
341
|
var SHEET_HEIGHT = SCREEN_HEIGHT * 0.55;
|
|
314
342
|
var DISMISS_THRESHOLD = 80;
|
|
343
|
+
var NEON = {
|
|
344
|
+
accent: "#0FFF50",
|
|
345
|
+
accentHover: "#00E646",
|
|
346
|
+
ink: "#0a1a0a",
|
|
347
|
+
border: "#00C43A"
|
|
348
|
+
};
|
|
315
349
|
var CATEGORIES = [
|
|
316
350
|
{ key: "bug", emoji: "\u{1F41B}", label: "Bug" },
|
|
317
351
|
{ key: "slow", emoji: "\u{1F422}", label: "Slow" },
|
|
@@ -458,13 +492,15 @@ var MushiBottomSheet = ({
|
|
|
458
492
|
}
|
|
459
493
|
};
|
|
460
494
|
const activeScreenshot = screenshotDataUrl && screenshotAttached ? screenshotDataUrl : null;
|
|
461
|
-
const
|
|
495
|
+
const pal = mushiPalette(dark ? "dark" : "light");
|
|
496
|
+
const colors = dark ? { bg: pal.paper, text: pal.ink, sub: pal.inkMuted, card: pal.paperRaised, accent: NEON.accent, accentInk: NEON.ink, border: pal.ruleStrong, backdrop: "rgba(0,0,0,0.6)", disabled: "#3a3a3c", disabledText: pal.inkFaint } : { bg: pal.paperRaised, text: pal.ink, sub: pal.inkMuted, card: pal.paper, accent: NEON.accent, accentInk: NEON.ink, border: pal.ruleStrong, backdrop: "rgba(0,0,0,0.35)", disabled: "#d1d1d6", disabledText: pal.inkFaint };
|
|
462
497
|
const canSubmit = !!category && description.trim().length > 0 && phase === "form";
|
|
463
498
|
return /* @__PURE__ */ jsx(Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, children: /* @__PURE__ */ jsxs(
|
|
464
499
|
KeyboardAvoidingView,
|
|
465
500
|
{
|
|
466
501
|
style: s.flex,
|
|
467
|
-
behavior: Platform2.OS === "ios" ? "padding" :
|
|
502
|
+
behavior: Platform2.OS === "ios" ? "padding" : "height",
|
|
503
|
+
keyboardVerticalOffset: Platform2.OS === "ios" ? 0 : 20,
|
|
468
504
|
children: [
|
|
469
505
|
/* @__PURE__ */ jsx(
|
|
470
506
|
Animated.View,
|
|
@@ -482,7 +518,11 @@ var MushiBottomSheet = ({
|
|
|
482
518
|
],
|
|
483
519
|
children: [
|
|
484
520
|
/* @__PURE__ */ jsx(View, { ...panResponder.panHandlers, style: s.handleArea, children: /* @__PURE__ */ jsx(View, { style: [s.handle, { backgroundColor: colors.sub }] }) }),
|
|
485
|
-
/* @__PURE__ */
|
|
521
|
+
/* @__PURE__ */ jsxs(View, { style: [s.brandHeader, { backgroundColor: NEON.accent, borderBottomColor: NEON.border }], children: [
|
|
522
|
+
/* @__PURE__ */ jsx(Text, { style: [s.brandEyebrow, { color: NEON.ink }], children: "MUSHI \xB7 BETA" }),
|
|
523
|
+
/* @__PURE__ */ jsx(Text, { style: [s.brandTitle, { color: NEON.ink }], children: sheetTab === "inbox" ? "Your reports" : sheetTab === "community" ? "Community" : "Report an issue" })
|
|
524
|
+
] }),
|
|
525
|
+
/* @__PURE__ */ jsx(View, { style: s.tabRow, children: ["report", "inbox", "community"].map((tab) => /* @__PURE__ */ jsx(
|
|
486
526
|
TouchableOpacity,
|
|
487
527
|
{
|
|
488
528
|
onPress: () => {
|
|
@@ -491,11 +531,11 @@ var MushiBottomSheet = ({
|
|
|
491
531
|
else setSelectedReportId(null);
|
|
492
532
|
},
|
|
493
533
|
style: [s.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
|
|
494
|
-
children: /* @__PURE__ */ jsx(Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.
|
|
534
|
+
children: /* @__PURE__ */ jsx(Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.text : colors.sub }], children: tab === "report" ? "Report" : tab === "inbox" ? "Your reports" : "Community" })
|
|
495
535
|
},
|
|
496
536
|
tab
|
|
497
537
|
)) }),
|
|
498
|
-
sheetTab === "inbox" ? /* @__PURE__ */ jsx(
|
|
538
|
+
sheetTab === "inbox" ? /* @__PURE__ */ jsx(ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: inboxLoading ? /* @__PURE__ */ jsx(Text, { style: { color: colors.sub }, children: "Loading\u2026" }) : selectedReportId ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
499
539
|
/* @__PURE__ */ jsx(TouchableOpacity, { onPress: () => setSelectedReportId(null), children: /* @__PURE__ */ jsx(Text, { style: { color: colors.accent, marginBottom: 8 }, children: "\u2190 Back" }) }),
|
|
500
540
|
threadComments.map((c) => /* @__PURE__ */ jsxs(View, { style: [s.threadBubble, { backgroundColor: colors.card }], children: [
|
|
501
541
|
/* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11 }, children: c.author_kind }),
|
|
@@ -504,7 +544,7 @@ var MushiBottomSheet = ({
|
|
|
504
544
|
VERIFIABLE_STATUSES.has(
|
|
505
545
|
inboxReports.find((r) => r.id === selectedReportId)?.status ?? ""
|
|
506
546
|
) && /* @__PURE__ */ jsxs(View, { style: s.verifyRow, children: [
|
|
507
|
-
/* @__PURE__ */ jsx(TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.accent }], onPress: () => submitFeedback("confirms"), children: /* @__PURE__ */ jsx(Text, { style: s.submitText, children: "Yes, fixed" }) }),
|
|
547
|
+
/* @__PURE__ */ jsx(TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.accent }], onPress: () => submitFeedback("confirms"), children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentInk }], children: "Yes, fixed" }) }),
|
|
508
548
|
/* @__PURE__ */ jsx(TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.border }], onPress: () => submitFeedback("not_fixed"), children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.text }], children: "Not fixed" }) })
|
|
509
549
|
] }),
|
|
510
550
|
/* @__PURE__ */ jsx(
|
|
@@ -517,106 +557,130 @@ var MushiBottomSheet = ({
|
|
|
517
557
|
onChangeText: setReplyText
|
|
518
558
|
}
|
|
519
559
|
),
|
|
520
|
-
/* @__PURE__ */ jsx(TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], onPress: sendReply, children: /* @__PURE__ */ jsx(Text, { style: s.submitText, children: "Send" }) })
|
|
560
|
+
/* @__PURE__ */ jsx(TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], onPress: sendReply, children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentInk }], children: "Send" }) })
|
|
521
561
|
] }) : inboxReports.map((r) => /* @__PURE__ */ jsxs(TouchableOpacity, { style: [s.inboxRow, { borderColor: colors.border }], onPress: () => openThread(r.id), children: [
|
|
522
562
|
/* @__PURE__ */ jsx(Text, { style: { color: colors.text, fontWeight: "600" }, numberOfLines: 1, children: (r.summary ?? r.description ?? "Report").slice(0, 60) }),
|
|
523
563
|
/* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11 }, children: r.status })
|
|
524
|
-
] }, r.id)) }) :
|
|
564
|
+
] }, r.id)) }) : sheetTab === "community" ? /* @__PURE__ */ jsxs(ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: [
|
|
565
|
+
/* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 13, marginBottom: 16, lineHeight: 18 }, children: "Sign in to track your reports across apps and see the global leaderboard. No password needed." }),
|
|
566
|
+
/* @__PURE__ */ jsx(
|
|
567
|
+
TextInput,
|
|
568
|
+
{
|
|
569
|
+
style: [s.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border, minHeight: 48 }],
|
|
570
|
+
placeholder: "you@example.com",
|
|
571
|
+
placeholderTextColor: colors.sub,
|
|
572
|
+
keyboardType: "email-address",
|
|
573
|
+
autoCapitalize: "none",
|
|
574
|
+
autoCorrect: false,
|
|
575
|
+
textContentType: "emailAddress"
|
|
576
|
+
}
|
|
577
|
+
),
|
|
578
|
+
/* @__PURE__ */ jsx(TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: colors.accentInk }], children: "Send sign-in link \u2192" }) }),
|
|
579
|
+
/* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11, textAlign: "center", marginTop: 12 }, children: "We'll email you a one-tap sign-in link. No password, ever." })
|
|
580
|
+
] }) : phase === "sent" ? /* @__PURE__ */ jsxs(View, { style: s.sentWrap, children: [
|
|
525
581
|
/* @__PURE__ */ jsx(Text, { style: [s.sentEmoji], children: "\u2705" }),
|
|
526
582
|
/* @__PURE__ */ jsx(Text, { style: [s.sentText, { color: colors.text }], children: "Report sent!" })
|
|
527
|
-
] }) : /* @__PURE__ */ jsxs(
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
583
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
584
|
+
ScrollView,
|
|
585
|
+
{
|
|
586
|
+
style: s.body,
|
|
587
|
+
keyboardShouldPersistTaps: "handled",
|
|
588
|
+
contentContainerStyle: { paddingBottom: 24 },
|
|
589
|
+
children: [
|
|
590
|
+
/* @__PURE__ */ jsx(Text, { style: [s.stepLabel, { color: colors.sub }], children: "What kind of issue?" }),
|
|
591
|
+
/* @__PURE__ */ jsx(View, { style: s.catRow, children: CATEGORIES.map((c) => {
|
|
592
|
+
const active = category === c.key;
|
|
593
|
+
return /* @__PURE__ */ jsxs(
|
|
594
|
+
TouchableOpacity,
|
|
538
595
|
{
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
596
|
+
onPress: () => setCategory(c.key),
|
|
597
|
+
activeOpacity: 0.7,
|
|
598
|
+
style: [
|
|
599
|
+
s.catBtn,
|
|
600
|
+
{
|
|
601
|
+
backgroundColor: active ? colors.accent : colors.card,
|
|
602
|
+
borderColor: active ? colors.accent : colors.border
|
|
603
|
+
}
|
|
604
|
+
],
|
|
605
|
+
children: [
|
|
606
|
+
/* @__PURE__ */ jsx(Text, { style: s.catEmoji, children: c.emoji }),
|
|
607
|
+
/* @__PURE__ */ jsx(
|
|
608
|
+
Text,
|
|
609
|
+
{
|
|
610
|
+
style: [
|
|
611
|
+
s.catLabel,
|
|
612
|
+
{ color: active ? colors.accentInk : colors.text }
|
|
613
|
+
],
|
|
614
|
+
children: c.label
|
|
615
|
+
}
|
|
616
|
+
)
|
|
617
|
+
]
|
|
618
|
+
},
|
|
619
|
+
c.key
|
|
620
|
+
);
|
|
621
|
+
}) }),
|
|
622
|
+
/* @__PURE__ */ jsx(
|
|
623
|
+
TextInput,
|
|
624
|
+
{
|
|
625
|
+
style: [
|
|
626
|
+
s.input,
|
|
547
627
|
{
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
],
|
|
552
|
-
children: c.label
|
|
628
|
+
backgroundColor: colors.card,
|
|
629
|
+
color: colors.text,
|
|
630
|
+
borderColor: colors.border
|
|
553
631
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
632
|
+
],
|
|
633
|
+
placeholder: "What happened?",
|
|
634
|
+
placeholderTextColor: colors.sub,
|
|
635
|
+
multiline: true,
|
|
636
|
+
textAlignVertical: "top",
|
|
637
|
+
value: description,
|
|
638
|
+
onChangeText: setDescription,
|
|
639
|
+
editable: phase === "form"
|
|
640
|
+
}
|
|
641
|
+
),
|
|
642
|
+
activeScreenshot ? /* @__PURE__ */ jsxs(View, { style: s.screenshotRow, children: [
|
|
643
|
+
/* @__PURE__ */ jsx(
|
|
644
|
+
Image,
|
|
645
|
+
{
|
|
646
|
+
source: { uri: activeScreenshot },
|
|
647
|
+
style: s.screenshotThumb,
|
|
648
|
+
accessibilityLabel: "Attached screenshot"
|
|
649
|
+
}
|
|
650
|
+
),
|
|
651
|
+
/* @__PURE__ */ jsxs(View, { style: s.screenshotMeta, children: [
|
|
652
|
+
/* @__PURE__ */ jsx(Text, { style: [s.screenshotLabel, { color: colors.text }], children: "Screenshot attached" }),
|
|
653
|
+
/* @__PURE__ */ jsx(Text, { style: [s.screenshotSub, { color: colors.sub }], children: "Helps the team see the issue" })
|
|
654
|
+
] }),
|
|
655
|
+
/* @__PURE__ */ jsx(
|
|
656
|
+
TouchableOpacity,
|
|
657
|
+
{
|
|
658
|
+
onPress: () => {
|
|
659
|
+
setScreenshotAttached(false);
|
|
660
|
+
onClearScreenshot?.();
|
|
661
|
+
},
|
|
662
|
+
hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
|
|
663
|
+
accessibilityLabel: "Remove screenshot",
|
|
664
|
+
children: /* @__PURE__ */ jsx(Text, { style: [s.screenshotRemove, { color: colors.sub }], children: "\u2715" })
|
|
665
|
+
}
|
|
666
|
+
)
|
|
667
|
+
] }) : null,
|
|
668
|
+
/* @__PURE__ */ jsx(
|
|
669
|
+
TouchableOpacity,
|
|
565
670
|
{
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
671
|
+
onPress: handleSubmit,
|
|
672
|
+
disabled: !canSubmit,
|
|
673
|
+
activeOpacity: 0.8,
|
|
674
|
+
style: [
|
|
675
|
+
s.submitBtn,
|
|
676
|
+
{ backgroundColor: canSubmit ? colors.accent : colors.disabled }
|
|
677
|
+
],
|
|
678
|
+
children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: canSubmit ? colors.accentInk : colors.disabledText }], children: phase === "sending" ? "Sending\u2026" : "Submit report" })
|
|
569
679
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
textAlignVertical: "top",
|
|
575
|
-
value: description,
|
|
576
|
-
onChangeText: setDescription,
|
|
577
|
-
editable: phase === "form"
|
|
578
|
-
}
|
|
579
|
-
),
|
|
580
|
-
activeScreenshot ? /* @__PURE__ */ jsxs(View, { style: s.screenshotRow, children: [
|
|
581
|
-
/* @__PURE__ */ jsx(
|
|
582
|
-
Image,
|
|
583
|
-
{
|
|
584
|
-
source: { uri: activeScreenshot },
|
|
585
|
-
style: s.screenshotThumb,
|
|
586
|
-
accessibilityLabel: "Attached screenshot"
|
|
587
|
-
}
|
|
588
|
-
),
|
|
589
|
-
/* @__PURE__ */ jsxs(View, { style: s.screenshotMeta, children: [
|
|
590
|
-
/* @__PURE__ */ jsx(Text, { style: [s.screenshotLabel, { color: colors.text }], children: "Screenshot attached" }),
|
|
591
|
-
/* @__PURE__ */ jsx(Text, { style: [s.screenshotSub, { color: colors.sub }], children: "Helps the team see the issue" })
|
|
592
|
-
] }),
|
|
593
|
-
/* @__PURE__ */ jsx(
|
|
594
|
-
TouchableOpacity,
|
|
595
|
-
{
|
|
596
|
-
onPress: () => {
|
|
597
|
-
setScreenshotAttached(false);
|
|
598
|
-
onClearScreenshot?.();
|
|
599
|
-
},
|
|
600
|
-
hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
|
|
601
|
-
accessibilityLabel: "Remove screenshot",
|
|
602
|
-
children: /* @__PURE__ */ jsx(Text, { style: [s.screenshotRemove, { color: colors.sub }], children: "\u2715" })
|
|
603
|
-
}
|
|
604
|
-
)
|
|
605
|
-
] }) : null,
|
|
606
|
-
/* @__PURE__ */ jsx(
|
|
607
|
-
TouchableOpacity,
|
|
608
|
-
{
|
|
609
|
-
onPress: handleSubmit,
|
|
610
|
-
disabled: !canSubmit,
|
|
611
|
-
activeOpacity: 0.8,
|
|
612
|
-
style: [
|
|
613
|
-
s.submitBtn,
|
|
614
|
-
{ backgroundColor: canSubmit ? colors.accent : colors.border }
|
|
615
|
-
],
|
|
616
|
-
children: /* @__PURE__ */ jsx(Text, { style: s.submitText, children: phase === "sending" ? "Sending\u2026" : "Submit" })
|
|
617
|
-
}
|
|
618
|
-
)
|
|
619
|
-
] })
|
|
680
|
+
)
|
|
681
|
+
]
|
|
682
|
+
}
|
|
683
|
+
)
|
|
620
684
|
]
|
|
621
685
|
}
|
|
622
686
|
)
|
|
@@ -654,6 +718,24 @@ var s = StyleSheet.create({
|
|
|
654
718
|
borderRadius: 3,
|
|
655
719
|
opacity: 0.5
|
|
656
720
|
},
|
|
721
|
+
brandHeader: {
|
|
722
|
+
paddingHorizontal: 20,
|
|
723
|
+
paddingTop: 10,
|
|
724
|
+
paddingBottom: 12,
|
|
725
|
+
borderBottomWidth: 1.5
|
|
726
|
+
},
|
|
727
|
+
brandEyebrow: {
|
|
728
|
+
fontSize: 10,
|
|
729
|
+
fontWeight: "700",
|
|
730
|
+
letterSpacing: 1.2,
|
|
731
|
+
marginBottom: 2,
|
|
732
|
+
opacity: 0.7
|
|
733
|
+
},
|
|
734
|
+
brandTitle: {
|
|
735
|
+
fontSize: 18,
|
|
736
|
+
fontWeight: "800",
|
|
737
|
+
letterSpacing: -0.2
|
|
738
|
+
},
|
|
657
739
|
body: {
|
|
658
740
|
paddingHorizontal: 20,
|
|
659
741
|
paddingTop: 4
|
|
@@ -663,6 +745,14 @@ var s = StyleSheet.create({
|
|
|
663
745
|
fontWeight: "700",
|
|
664
746
|
marginBottom: 16
|
|
665
747
|
},
|
|
748
|
+
stepLabel: {
|
|
749
|
+
fontSize: 12,
|
|
750
|
+
fontWeight: "600",
|
|
751
|
+
letterSpacing: 0.2,
|
|
752
|
+
marginTop: 8,
|
|
753
|
+
marginBottom: 10,
|
|
754
|
+
textTransform: "uppercase"
|
|
755
|
+
},
|
|
666
756
|
catRow: {
|
|
667
757
|
flexDirection: "row",
|
|
668
758
|
justifyContent: "space-between",
|
|
@@ -789,24 +879,144 @@ var s = StyleSheet.create({
|
|
|
789
879
|
});
|
|
790
880
|
|
|
791
881
|
// src/components/MushiFloatingButton.tsx
|
|
792
|
-
import { useRef as useRef2, useCallback as useCallback2 } from "react";
|
|
882
|
+
import { useRef as useRef2, useCallback as useCallback2, useEffect as useEffect2 } from "react";
|
|
793
883
|
import {
|
|
794
884
|
Animated as Animated2,
|
|
795
|
-
|
|
885
|
+
PanResponder as PanResponder2,
|
|
796
886
|
StyleSheet as StyleSheet2,
|
|
797
887
|
Platform as Platform3,
|
|
888
|
+
Dimensions as Dimensions3,
|
|
798
889
|
useColorScheme as useColorScheme2
|
|
799
890
|
} from "react-native";
|
|
891
|
+
import { mushiPalette as mushiPalette2, MUSHI_COPY } from "@mushi-mushi/core";
|
|
800
892
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
893
|
+
var FAB_SIZE = 56;
|
|
894
|
+
var EDGE_PADDING = 12;
|
|
895
|
+
function getScreenBounds() {
|
|
896
|
+
const { width, height } = Dimensions3.get("window");
|
|
897
|
+
return { width, height };
|
|
898
|
+
}
|
|
899
|
+
function clamp(value, min, max) {
|
|
900
|
+
return Math.min(Math.max(value, min), max);
|
|
901
|
+
}
|
|
902
|
+
var STORAGE_KEY = "mushi:fab-pos";
|
|
903
|
+
async function loadStoredPos() {
|
|
904
|
+
try {
|
|
905
|
+
const AsyncStorage = (await import("@react-native-async-storage/async-storage")).default;
|
|
906
|
+
const raw = await AsyncStorage.getItem(STORAGE_KEY);
|
|
907
|
+
if (!raw) return null;
|
|
908
|
+
return JSON.parse(raw);
|
|
909
|
+
} catch {
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
async function storePos(pos) {
|
|
914
|
+
try {
|
|
915
|
+
const AsyncStorage = (await import("@react-native-async-storage/async-storage")).default;
|
|
916
|
+
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(pos));
|
|
917
|
+
} catch {
|
|
918
|
+
}
|
|
919
|
+
}
|
|
801
920
|
var MushiFloatingButton = ({
|
|
802
921
|
onPress,
|
|
803
922
|
position = "bottom-right",
|
|
804
|
-
inset
|
|
923
|
+
inset,
|
|
924
|
+
config,
|
|
925
|
+
accent
|
|
805
926
|
}) => {
|
|
806
927
|
const scheme = useColorScheme2();
|
|
807
928
|
const dark = scheme === "dark";
|
|
929
|
+
const draggable = config?.draggable ?? false;
|
|
930
|
+
const snapToEdge = config?.snapToEdge ?? true;
|
|
931
|
+
const persist = config?.persist ?? false;
|
|
932
|
+
const baseBottom = inset?.bottom ?? (Platform3.OS === "ios" ? 50 : 28);
|
|
933
|
+
const baseX = position === "bottom-left" ? inset?.left ?? EDGE_PADDING : (() => {
|
|
934
|
+
const { width } = getScreenBounds();
|
|
935
|
+
return width - FAB_SIZE - (inset?.right ?? EDGE_PADDING);
|
|
936
|
+
})();
|
|
808
937
|
const scale = useRef2(new Animated2.Value(1)).current;
|
|
938
|
+
const posX = useRef2(new Animated2.Value(baseX)).current;
|
|
939
|
+
const posY = useRef2(new Animated2.Value(0)).current;
|
|
940
|
+
const currentX = useRef2(baseX);
|
|
941
|
+
const currentY = useRef2(0);
|
|
942
|
+
const isDragging = useRef2(false);
|
|
943
|
+
const accentColor = accent ?? mushiPalette2(dark ? "dark" : "light").accent;
|
|
944
|
+
useEffect2(() => {
|
|
945
|
+
if (!persist) return;
|
|
946
|
+
void loadStoredPos().then((pos) => {
|
|
947
|
+
if (!pos) return;
|
|
948
|
+
currentX.current = pos.x;
|
|
949
|
+
currentY.current = pos.y;
|
|
950
|
+
posX.setValue(pos.x);
|
|
951
|
+
posY.setValue(pos.y);
|
|
952
|
+
});
|
|
953
|
+
}, [persist, posX, posY]);
|
|
954
|
+
const snapAndPersist = useCallback2(
|
|
955
|
+
(x, y) => {
|
|
956
|
+
const { width, height } = getScreenBounds();
|
|
957
|
+
let snappedX = x;
|
|
958
|
+
if (snapToEdge) {
|
|
959
|
+
const midX = width / 2;
|
|
960
|
+
snappedX = x + FAB_SIZE / 2 < midX ? EDGE_PADDING : width - FAB_SIZE - EDGE_PADDING;
|
|
961
|
+
}
|
|
962
|
+
const clampedX = clamp(snappedX, EDGE_PADDING, width - FAB_SIZE - EDGE_PADDING);
|
|
963
|
+
const clampedY = clamp(y, -(height - FAB_SIZE - baseBottom - 60), 0);
|
|
964
|
+
currentX.current = clampedX;
|
|
965
|
+
currentY.current = clampedY;
|
|
966
|
+
Animated2.spring(posX, {
|
|
967
|
+
toValue: clampedX,
|
|
968
|
+
useNativeDriver: false,
|
|
969
|
+
damping: 18,
|
|
970
|
+
stiffness: 220
|
|
971
|
+
}).start();
|
|
972
|
+
Animated2.spring(posY, {
|
|
973
|
+
toValue: clampedY,
|
|
974
|
+
useNativeDriver: false,
|
|
975
|
+
damping: 18,
|
|
976
|
+
stiffness: 220
|
|
977
|
+
}).start();
|
|
978
|
+
if (persist) void storePos({ x: clampedX, y: clampedY });
|
|
979
|
+
},
|
|
980
|
+
[snapToEdge, persist, posX, posY, baseBottom]
|
|
981
|
+
);
|
|
982
|
+
const panResponder = useRef2(
|
|
983
|
+
draggable ? PanResponder2.create({
|
|
984
|
+
onStartShouldSetPanResponder: () => true,
|
|
985
|
+
onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dx) > 6 || Math.abs(g.dy) > 6,
|
|
986
|
+
onPanResponderGrant: () => {
|
|
987
|
+
isDragging.current = false;
|
|
988
|
+
posX.stopAnimation();
|
|
989
|
+
posY.stopAnimation();
|
|
990
|
+
},
|
|
991
|
+
onPanResponderMove: (_, g) => {
|
|
992
|
+
if (Math.abs(g.dx) > 6 || Math.abs(g.dy) > 6) {
|
|
993
|
+
isDragging.current = true;
|
|
994
|
+
scale.setValue(1);
|
|
995
|
+
}
|
|
996
|
+
const { width, height } = getScreenBounds();
|
|
997
|
+
const newX = clamp(
|
|
998
|
+
currentX.current + g.dx,
|
|
999
|
+
EDGE_PADDING,
|
|
1000
|
+
width - FAB_SIZE - EDGE_PADDING
|
|
1001
|
+
);
|
|
1002
|
+
const newY = clamp(
|
|
1003
|
+
currentY.current + g.dy,
|
|
1004
|
+
-(height - FAB_SIZE - baseBottom - 60),
|
|
1005
|
+
0
|
|
1006
|
+
);
|
|
1007
|
+
posX.setValue(newX);
|
|
1008
|
+
posY.setValue(newY);
|
|
1009
|
+
},
|
|
1010
|
+
onPanResponderRelease: (_, g) => {
|
|
1011
|
+
if (isDragging.current) {
|
|
1012
|
+
snapAndPersist(currentX.current + g.dx, currentY.current + g.dy);
|
|
1013
|
+
}
|
|
1014
|
+
isDragging.current = false;
|
|
1015
|
+
}
|
|
1016
|
+
}) : PanResponder2.create({})
|
|
1017
|
+
).current;
|
|
809
1018
|
const handlePressIn = useCallback2(() => {
|
|
1019
|
+
if (isDragging.current) return;
|
|
810
1020
|
Animated2.spring(scale, {
|
|
811
1021
|
toValue: 0.85,
|
|
812
1022
|
useNativeDriver: true,
|
|
@@ -821,39 +1031,35 @@ var MushiFloatingButton = ({
|
|
|
821
1031
|
damping: 10,
|
|
822
1032
|
stiffness: 250
|
|
823
1033
|
}).start();
|
|
824
|
-
|
|
825
|
-
|
|
1034
|
+
if (!isDragging.current) onPress();
|
|
1035
|
+
}, [scale, onPress]);
|
|
826
1036
|
return /* @__PURE__ */ jsx2(
|
|
827
|
-
|
|
1037
|
+
Animated2.View,
|
|
828
1038
|
{
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
children: /* @__PURE__ */ jsx2(
|
|
833
|
-
Animated2.View,
|
|
1039
|
+
...draggable ? panResponder.panHandlers : {},
|
|
1040
|
+
style: [
|
|
1041
|
+
s2.fab,
|
|
834
1042
|
{
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
backgroundColor: dark ? "#0a84ff" : "#007aff",
|
|
841
|
-
transform: [{ scale }],
|
|
842
|
-
shadowColor: dark ? "#0a84ff" : "#000"
|
|
843
|
-
}
|
|
844
|
-
],
|
|
845
|
-
children: /* @__PURE__ */ jsx2(Animated2.Text, { style: s2.emoji, children: "\u{1F41B}" })
|
|
1043
|
+
left: posX,
|
|
1044
|
+
bottom: Animated2.add(new Animated2.Value(baseBottom), Animated2.multiply(posY, -1)),
|
|
1045
|
+
backgroundColor: accentColor,
|
|
1046
|
+
shadowColor: dark ? accentColor : "#000",
|
|
1047
|
+
transform: [{ scale }]
|
|
846
1048
|
}
|
|
847
|
-
|
|
1049
|
+
],
|
|
1050
|
+
onStartShouldSetResponder: () => !draggable,
|
|
1051
|
+
onResponderGrant: handlePressIn,
|
|
1052
|
+
onResponderRelease: handlePressOut,
|
|
1053
|
+
children: /* @__PURE__ */ jsx2(Animated2.Text, { style: s2.emoji, children: MUSHI_COPY.triggerText })
|
|
848
1054
|
}
|
|
849
1055
|
);
|
|
850
1056
|
};
|
|
851
1057
|
var s2 = StyleSheet2.create({
|
|
852
1058
|
fab: {
|
|
853
1059
|
position: "absolute",
|
|
854
|
-
width:
|
|
855
|
-
height:
|
|
856
|
-
borderRadius:
|
|
1060
|
+
width: FAB_SIZE,
|
|
1061
|
+
height: FAB_SIZE,
|
|
1062
|
+
borderRadius: FAB_SIZE / 2,
|
|
857
1063
|
alignItems: "center",
|
|
858
1064
|
justifyContent: "center",
|
|
859
1065
|
zIndex: 9999,
|
|
@@ -867,14 +1073,53 @@ var s2 = StyleSheet2.create({
|
|
|
867
1073
|
}
|
|
868
1074
|
});
|
|
869
1075
|
|
|
1076
|
+
// src/version.ts
|
|
1077
|
+
var MUSHI_SDK_PACKAGE = "@mushi-mushi/react-native";
|
|
1078
|
+
var MUSHI_SDK_VERSION = "0.17.0" ? "0.17.0" : "0.17.0";
|
|
1079
|
+
|
|
870
1080
|
// src/provider.tsx
|
|
871
1081
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
872
1082
|
var MushiContext = createContext(null);
|
|
873
|
-
function
|
|
1083
|
+
function breadcrumbToTimelineKind(category) {
|
|
1084
|
+
switch (category) {
|
|
1085
|
+
case "navigation":
|
|
1086
|
+
return "route";
|
|
1087
|
+
case "ui.click":
|
|
1088
|
+
case "ui.tap":
|
|
1089
|
+
return "click";
|
|
1090
|
+
case "xhr":
|
|
1091
|
+
case "fetch":
|
|
1092
|
+
case "network":
|
|
1093
|
+
return "request";
|
|
1094
|
+
default:
|
|
1095
|
+
return "log";
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
1099
|
+
const proc = globalThis.process;
|
|
1100
|
+
const envProjectId = proc?.env?.MUSHI_PROJECT_ID || proc?.env?.EXPO_PUBLIC_MUSHI_PROJECT_ID;
|
|
1101
|
+
const envApiKey = proc?.env?.MUSHI_API_KEY || proc?.env?.EXPO_PUBLIC_MUSHI_API_KEY;
|
|
1102
|
+
const config = {
|
|
1103
|
+
...envProjectId ? { projectId: envProjectId } : {},
|
|
1104
|
+
...envApiKey ? { apiKey: envApiKey } : {},
|
|
1105
|
+
...configProp,
|
|
1106
|
+
...barePropConfig
|
|
1107
|
+
};
|
|
1108
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
1109
|
+
if (!config.projectId || !config.apiKey) {
|
|
1110
|
+
console.warn(
|
|
1111
|
+
"[MushiProvider] Missing projectId or apiKey \u2014 set MUSHI_PROJECT_ID / MUSHI_API_KEY (or EXPO_PUBLIC_* equivalents) or pass them as props."
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
874
1115
|
const consoleRef = useRef3(null);
|
|
875
1116
|
const networkRef = useRef3(null);
|
|
876
1117
|
const queueRef = useRef3(null);
|
|
877
1118
|
const apiClientRef = useRef3(null);
|
|
1119
|
+
const sessionIdRef = useRef3(`sess_${newUuid()}`);
|
|
1120
|
+
const breadcrumbsRef = useRef3(
|
|
1121
|
+
createBreadcrumbBuffer({ max: config.capture?.maxBreadcrumbs ?? 50 })
|
|
1122
|
+
);
|
|
878
1123
|
const [sheetVisible, setSheetVisible] = useState2(false);
|
|
879
1124
|
const [sheetScreenshot, setSheetScreenshot] = useState2(null);
|
|
880
1125
|
const reporterTokenRef = useRef3(`rn-${config.projectId}-anon`);
|
|
@@ -885,6 +1130,8 @@ function MushiProvider({ children, ...config }) {
|
|
|
885
1130
|
resolveReporterTokenReady = resolve;
|
|
886
1131
|
})
|
|
887
1132
|
);
|
|
1133
|
+
const userTokenRef = useRef3(null);
|
|
1134
|
+
const pageContextRef = useRef3(null);
|
|
888
1135
|
const rawEndpoint = config.apiEndpoint ?? config.endpoint;
|
|
889
1136
|
if (rawEndpoint !== void 0 && rawEndpoint.trim() === "") {
|
|
890
1137
|
throw new Error(
|
|
@@ -895,7 +1142,7 @@ function MushiProvider({ children, ...config }) {
|
|
|
895
1142
|
console.warn("[MushiProvider] `endpoint` is deprecated \u2014 use `apiEndpoint` instead.");
|
|
896
1143
|
}
|
|
897
1144
|
const apiEndpoint = rawEndpoint ?? DEFAULT_API_ENDPOINT;
|
|
898
|
-
|
|
1145
|
+
useEffect3(() => {
|
|
899
1146
|
if (config.capture?.console !== false) {
|
|
900
1147
|
consoleRef.current = setupConsoleCapture(config.capture?.maxConsoleEntries);
|
|
901
1148
|
}
|
|
@@ -905,7 +1152,8 @@ function MushiProvider({ children, ...config }) {
|
|
|
905
1152
|
apiClientRef.current = createApiClient({
|
|
906
1153
|
projectId: config.projectId,
|
|
907
1154
|
apiKey: config.apiKey,
|
|
908
|
-
apiEndpoint
|
|
1155
|
+
apiEndpoint,
|
|
1156
|
+
getUserToken: () => userTokenRef.current
|
|
909
1157
|
});
|
|
910
1158
|
queueRef.current = new AsyncStorageQueue({
|
|
911
1159
|
maxSize: config.storage?.maxQueueSize,
|
|
@@ -918,6 +1166,12 @@ function MushiProvider({ children, ...config }) {
|
|
|
918
1166
|
});
|
|
919
1167
|
queueRef.current.flush().catch(() => {
|
|
920
1168
|
});
|
|
1169
|
+
breadcrumbsRef.current.add({
|
|
1170
|
+
category: "lifecycle",
|
|
1171
|
+
level: "info",
|
|
1172
|
+
message: "Mushi SDK initialised",
|
|
1173
|
+
data: { sdk: MUSHI_SDK_PACKAGE, version: MUSHI_SDK_VERSION }
|
|
1174
|
+
});
|
|
921
1175
|
(async () => {
|
|
922
1176
|
const TOKEN_KEY = "@mushi:reporter_token";
|
|
923
1177
|
try {
|
|
@@ -944,7 +1198,7 @@ function MushiProvider({ children, ...config }) {
|
|
|
944
1198
|
networkRef.current?.restore();
|
|
945
1199
|
};
|
|
946
1200
|
}, []);
|
|
947
|
-
|
|
1201
|
+
useEffect3(() => {
|
|
948
1202
|
let unsubscribe;
|
|
949
1203
|
try {
|
|
950
1204
|
const NetInfo = __require("@react-native-community/netinfo").default;
|
|
@@ -959,6 +1213,17 @@ function MushiProvider({ children, ...config }) {
|
|
|
959
1213
|
return () => unsubscribe?.();
|
|
960
1214
|
}, []);
|
|
961
1215
|
const open = useCallback3(() => {
|
|
1216
|
+
breadcrumbsRef.current.add({
|
|
1217
|
+
category: "lifecycle",
|
|
1218
|
+
level: "info",
|
|
1219
|
+
message: "Widget opened",
|
|
1220
|
+
...screenRef.current?.name ? { data: { screen: screenRef.current.name } } : {}
|
|
1221
|
+
});
|
|
1222
|
+
if (config.capture?.screenshot === false) {
|
|
1223
|
+
setSheetScreenshot(null);
|
|
1224
|
+
setSheetVisible(true);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
962
1227
|
let vshot = null;
|
|
963
1228
|
try {
|
|
964
1229
|
vshot = __require("react-native-view-shot");
|
|
@@ -976,10 +1241,10 @@ function MushiProvider({ children, ...config }) {
|
|
|
976
1241
|
}).finally(() => {
|
|
977
1242
|
setSheetVisible(true);
|
|
978
1243
|
});
|
|
979
|
-
}, []);
|
|
1244
|
+
}, [config.capture?.screenshot]);
|
|
980
1245
|
const close = useCallback3(() => setSheetVisible(false), []);
|
|
981
1246
|
const attachTo = useCallback3(() => ({ onPress: open }), [open]);
|
|
982
|
-
|
|
1247
|
+
useEffect3(() => {
|
|
983
1248
|
const trigger2 = config.widget?.trigger ?? "button";
|
|
984
1249
|
if (trigger2 !== "shake" && trigger2 !== "both") return;
|
|
985
1250
|
let disposed = false;
|
|
@@ -1010,14 +1275,35 @@ function MushiProvider({ children, ...config }) {
|
|
|
1010
1275
|
const submitReport = useCallback3(
|
|
1011
1276
|
async (data) => {
|
|
1012
1277
|
const deviceInfo = getDeviceInfo();
|
|
1278
|
+
breadcrumbsRef.current.add({
|
|
1279
|
+
category: "lifecycle",
|
|
1280
|
+
level: "info",
|
|
1281
|
+
message: "Report submitted",
|
|
1282
|
+
data: { category: data.category }
|
|
1283
|
+
});
|
|
1013
1284
|
const contextMeta = {};
|
|
1014
|
-
if (userRef.current?.id)
|
|
1285
|
+
if (userRef.current?.id) {
|
|
1286
|
+
contextMeta.user = {
|
|
1287
|
+
id: userRef.current.id,
|
|
1288
|
+
...userRef.current.email ? { email: userRef.current.email } : {},
|
|
1289
|
+
...userRef.current.name ? { name: userRef.current.name } : {},
|
|
1290
|
+
...userRef.current.provider ? { provider: userRef.current.provider } : {}
|
|
1291
|
+
};
|
|
1292
|
+
contextMeta.userId = userRef.current.id;
|
|
1293
|
+
}
|
|
1015
1294
|
if (userRef.current?.email) contextMeta.userEmail = userRef.current.email;
|
|
1016
1295
|
if (userRef.current?.name) contextMeta.userName = userRef.current.name;
|
|
1017
1296
|
if (screenRef.current?.name) contextMeta.screenName = screenRef.current.name;
|
|
1018
1297
|
if (screenRef.current?.route) contextMeta.screenRoute = screenRef.current.route;
|
|
1019
1298
|
if (screenRef.current?.feature) contextMeta.screenFeature = screenRef.current.feature;
|
|
1020
1299
|
const merged = Object.keys(metadataRef.current).length > 0 ? { ...contextMeta, ...metadataRef.current } : Object.keys(contextMeta).length > 0 ? contextMeta : void 0;
|
|
1300
|
+
const breadcrumbs = breadcrumbsRef.current.getAll();
|
|
1301
|
+
const timeline = breadcrumbs.map((c) => ({
|
|
1302
|
+
ts: c.timestamp,
|
|
1303
|
+
kind: breadcrumbToTimelineKind(c.category),
|
|
1304
|
+
payload: { message: c.message, level: c.level, ...c.data ?? {} }
|
|
1305
|
+
}));
|
|
1306
|
+
const fingerprintHash = getDeviceFingerprintHash(deviceInfo);
|
|
1021
1307
|
const report = {
|
|
1022
1308
|
id: newUuid(),
|
|
1023
1309
|
projectId: config.projectId,
|
|
@@ -1040,7 +1326,14 @@ function MushiProvider({ children, ...config }) {
|
|
|
1040
1326
|
})) ?? [],
|
|
1041
1327
|
screenshotDataUrl: data.screenshotDataUrl,
|
|
1042
1328
|
metadata: merged,
|
|
1329
|
+
sessionId: sessionIdRef.current,
|
|
1043
1330
|
reporterToken: reporterTokenRef.current,
|
|
1331
|
+
...fingerprintHash ? { fingerprintHash } : {},
|
|
1332
|
+
...deviceInfo.appVersion ? { appVersion: deviceInfo.appVersion } : {},
|
|
1333
|
+
sdkPackage: MUSHI_SDK_PACKAGE,
|
|
1334
|
+
sdkVersion: MUSHI_SDK_VERSION,
|
|
1335
|
+
...breadcrumbs.length > 0 ? { breadcrumbs } : {},
|
|
1336
|
+
...timeline.length > 0 ? { timeline } : {},
|
|
1044
1337
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1045
1338
|
};
|
|
1046
1339
|
const client = apiClientRef.current;
|
|
@@ -1060,7 +1353,7 @@ function MushiProvider({ children, ...config }) {
|
|
|
1060
1353
|
const screenRef = useRef3(null);
|
|
1061
1354
|
const activityQueueRef = useRef3([]);
|
|
1062
1355
|
const rewardsFlushRef = useRef3(null);
|
|
1063
|
-
|
|
1356
|
+
useEffect3(() => {
|
|
1064
1357
|
const rewards = config.rewards;
|
|
1065
1358
|
if (!rewards?.enabled || !rewards.trackActivity) return;
|
|
1066
1359
|
const flushMs = Math.max(3e4, rewards.flushIntervalMs ?? 3e5);
|
|
@@ -1098,11 +1391,53 @@ function MushiProvider({ children, ...config }) {
|
|
|
1098
1391
|
provider: traits?.provider
|
|
1099
1392
|
};
|
|
1100
1393
|
},
|
|
1394
|
+
setUser(user) {
|
|
1395
|
+
userRef.current = {
|
|
1396
|
+
id: user.id,
|
|
1397
|
+
email: user.email,
|
|
1398
|
+
name: user.name,
|
|
1399
|
+
provider: user.provider
|
|
1400
|
+
};
|
|
1401
|
+
},
|
|
1402
|
+
identifyWithToken(token) {
|
|
1403
|
+
userTokenRef.current = token && typeof token === "string" ? token : null;
|
|
1404
|
+
if (userTokenRef.current) {
|
|
1405
|
+
const claims = parseIdentityToken(userTokenRef.current);
|
|
1406
|
+
if (claims?.sub) {
|
|
1407
|
+
userRef.current = {
|
|
1408
|
+
id: claims.sub,
|
|
1409
|
+
...claims.email ? { email: claims.email } : {},
|
|
1410
|
+
...claims.name ? { name: claims.name } : {}
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
},
|
|
1415
|
+
publishPageContext(context) {
|
|
1416
|
+
pageContextRef.current = context && context.route ? context : null;
|
|
1417
|
+
},
|
|
1101
1418
|
setMetadata(key, value) {
|
|
1102
1419
|
metadataRef.current[key] = value;
|
|
1103
1420
|
},
|
|
1104
1421
|
setScreen(screen) {
|
|
1422
|
+
const prev = screenRef.current?.name;
|
|
1105
1423
|
screenRef.current = screen;
|
|
1424
|
+
if (screen.name && screen.name !== prev) {
|
|
1425
|
+
breadcrumbsRef.current.add({
|
|
1426
|
+
category: "navigation",
|
|
1427
|
+
level: "info",
|
|
1428
|
+
message: `Navigated to ${screen.name}`,
|
|
1429
|
+
// Always include `route` (fallback to the screen name) so the
|
|
1430
|
+
// admin repro-timeline renders the destination instead of
|
|
1431
|
+
// "unknown route" when the host only set a screen name.
|
|
1432
|
+
data: {
|
|
1433
|
+
route: screen.route ?? screen.name,
|
|
1434
|
+
...screen.feature ? { feature: screen.feature } : {}
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
},
|
|
1439
|
+
addBreadcrumb(crumb) {
|
|
1440
|
+
breadcrumbsRef.current.add(crumb);
|
|
1106
1441
|
},
|
|
1107
1442
|
// Reporter API — scoped to the device's persistent reporter token
|
|
1108
1443
|
async listMyReports() {
|
|
@@ -1243,10 +1578,10 @@ function MushiTrigger({ children }) {
|
|
|
1243
1578
|
}
|
|
1244
1579
|
|
|
1245
1580
|
// src/capture/navigation-capture.ts
|
|
1246
|
-
import { useEffect as
|
|
1581
|
+
import { useEffect as useEffect4, useRef as useRef4 } from "react";
|
|
1247
1582
|
function useNavigationCapture(navigationRef) {
|
|
1248
1583
|
const historyRef = useRef4([]);
|
|
1249
|
-
|
|
1584
|
+
useEffect4(() => {
|
|
1250
1585
|
if (!navigationRef?.addListener || !navigationRef?.getCurrentRoute) return;
|
|
1251
1586
|
const unsubscribe = navigationRef.addListener("state", () => {
|
|
1252
1587
|
const route = navigationRef.getCurrentRoute?.();
|