@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.cjs
CHANGED
|
@@ -47,7 +47,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
47
47
|
|
|
48
48
|
// src/provider.tsx
|
|
49
49
|
var import_react3 = require("react");
|
|
50
|
-
var
|
|
50
|
+
var import_core3 = require("@mushi-mushi/core");
|
|
51
51
|
|
|
52
52
|
// src/capture/console-capture.ts
|
|
53
53
|
function setupConsoleCapture(maxEntries = 100) {
|
|
@@ -164,6 +164,30 @@ function getDeviceInfo() {
|
|
|
164
164
|
};
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
// src/capture/fingerprint.ts
|
|
168
|
+
function nonCryptoHashHex(input) {
|
|
169
|
+
let hash = 0;
|
|
170
|
+
for (let i = 0; i < input.length; i++) {
|
|
171
|
+
hash = (hash << 5) - hash + input.charCodeAt(i);
|
|
172
|
+
hash |= 0;
|
|
173
|
+
}
|
|
174
|
+
return `rnfp_${(hash >>> 0).toString(16).padStart(8, "0")}`;
|
|
175
|
+
}
|
|
176
|
+
function getDeviceFingerprintHash(device) {
|
|
177
|
+
const inputs = {
|
|
178
|
+
platform: device.platform,
|
|
179
|
+
osVersion: String(device.osVersion ?? ""),
|
|
180
|
+
screenWidth: device.screenWidth,
|
|
181
|
+
screenHeight: device.screenHeight,
|
|
182
|
+
pixelRatio: device.pixelRatio,
|
|
183
|
+
locale: device.locale ?? "",
|
|
184
|
+
timezone: device.timezone ?? ""
|
|
185
|
+
};
|
|
186
|
+
const serialised = JSON.stringify(inputs);
|
|
187
|
+
if (!device.platform && !device.screenWidth) return null;
|
|
188
|
+
return nonCryptoHashHex(serialised);
|
|
189
|
+
}
|
|
190
|
+
|
|
167
191
|
// src/storage/async-storage-queue.ts
|
|
168
192
|
var QUEUE_KEY = "@mushi:offline_queue";
|
|
169
193
|
var AsyncStorageQueue = class {
|
|
@@ -317,10 +341,17 @@ function sleep(ms) {
|
|
|
317
341
|
// src/components/MushiBottomSheet.tsx
|
|
318
342
|
var import_react = require("react");
|
|
319
343
|
var import_react_native2 = require("react-native");
|
|
344
|
+
var import_core = require("@mushi-mushi/core");
|
|
320
345
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
321
346
|
var { height: SCREEN_HEIGHT } = import_react_native2.Dimensions.get("window");
|
|
322
347
|
var SHEET_HEIGHT = SCREEN_HEIGHT * 0.55;
|
|
323
348
|
var DISMISS_THRESHOLD = 80;
|
|
349
|
+
var NEON = {
|
|
350
|
+
accent: "#0FFF50",
|
|
351
|
+
accentHover: "#00E646",
|
|
352
|
+
ink: "#0a1a0a",
|
|
353
|
+
border: "#00C43A"
|
|
354
|
+
};
|
|
324
355
|
var CATEGORIES = [
|
|
325
356
|
{ key: "bug", emoji: "\u{1F41B}", label: "Bug" },
|
|
326
357
|
{ key: "slow", emoji: "\u{1F422}", label: "Slow" },
|
|
@@ -467,13 +498,15 @@ var MushiBottomSheet = ({
|
|
|
467
498
|
}
|
|
468
499
|
};
|
|
469
500
|
const activeScreenshot = screenshotDataUrl && screenshotAttached ? screenshotDataUrl : null;
|
|
470
|
-
const
|
|
501
|
+
const pal = (0, import_core.mushiPalette)(dark ? "dark" : "light");
|
|
502
|
+
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 };
|
|
471
503
|
const canSubmit = !!category && description.trim().length > 0 && phase === "form";
|
|
472
504
|
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)(
|
|
473
505
|
import_react_native2.KeyboardAvoidingView,
|
|
474
506
|
{
|
|
475
507
|
style: s.flex,
|
|
476
|
-
behavior: import_react_native2.Platform.OS === "ios" ? "padding" :
|
|
508
|
+
behavior: import_react_native2.Platform.OS === "ios" ? "padding" : "height",
|
|
509
|
+
keyboardVerticalOffset: import_react_native2.Platform.OS === "ios" ? 0 : 20,
|
|
477
510
|
children: [
|
|
478
511
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
479
512
|
import_react_native2.Animated.View,
|
|
@@ -491,7 +524,11 @@ var MushiBottomSheet = ({
|
|
|
491
524
|
],
|
|
492
525
|
children: [
|
|
493
526
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { ...panResponder.panHandlers, style: s.handleArea, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [s.handle, { backgroundColor: colors.sub }] }) }),
|
|
494
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
527
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [s.brandHeader, { backgroundColor: NEON.accent, borderBottomColor: NEON.border }], children: [
|
|
528
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.brandEyebrow, { color: NEON.ink }], children: "MUSHI \xB7 BETA" }),
|
|
529
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.brandTitle, { color: NEON.ink }], children: sheetTab === "inbox" ? "Your reports" : sheetTab === "community" ? "Community" : "Report an issue" })
|
|
530
|
+
] }),
|
|
531
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.tabRow, children: ["report", "inbox", "community"].map((tab) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
495
532
|
import_react_native2.TouchableOpacity,
|
|
496
533
|
{
|
|
497
534
|
onPress: () => {
|
|
@@ -500,11 +537,11 @@ var MushiBottomSheet = ({
|
|
|
500
537
|
else setSelectedReportId(null);
|
|
501
538
|
},
|
|
502
539
|
style: [s.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
|
|
503
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.
|
|
540
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.tabLabel, { color: sheetTab === tab ? colors.text : colors.sub }], children: tab === "report" ? "Report" : tab === "inbox" ? "Your reports" : "Community" })
|
|
504
541
|
},
|
|
505
542
|
tab
|
|
506
543
|
)) }),
|
|
507
|
-
sheetTab === "inbox" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.
|
|
544
|
+
sheetTab === "inbox" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: inboxLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub }, children: "Loading\u2026" }) : selectedReportId ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
508
545
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { onPress: () => setSelectedReportId(null), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.accent, marginBottom: 8 }, children: "\u2190 Back" }) }),
|
|
509
546
|
threadComments.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: [s.threadBubble, { backgroundColor: colors.card }], children: [
|
|
510
547
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 11 }, children: c.author_kind }),
|
|
@@ -513,7 +550,7 @@ var MushiBottomSheet = ({
|
|
|
513
550
|
VERIFIABLE_STATUSES.has(
|
|
514
551
|
inboxReports.find((r) => r.id === selectedReportId)?.status ?? ""
|
|
515
552
|
) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.verifyRow, children: [
|
|
516
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.accent }], onPress: () => submitFeedback("confirms"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.submitText, children: "Yes, fixed" }) }),
|
|
553
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.accent }], onPress: () => submitFeedback("confirms"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.accentInk }], children: "Yes, fixed" }) }),
|
|
517
554
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.verifyBtn, { backgroundColor: colors.border }], onPress: () => submitFeedback("not_fixed"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.text }], children: "Not fixed" }) })
|
|
518
555
|
] }),
|
|
519
556
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
@@ -526,106 +563,130 @@ var MushiBottomSheet = ({
|
|
|
526
563
|
onChangeText: setReplyText
|
|
527
564
|
}
|
|
528
565
|
),
|
|
529
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], onPress: sendReply, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.submitText, children: "Send" }) })
|
|
566
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], onPress: sendReply, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.accentInk }], children: "Send" }) })
|
|
530
567
|
] }) : inboxReports.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.TouchableOpacity, { style: [s.inboxRow, { borderColor: colors.border }], onPress: () => openThread(r.id), children: [
|
|
531
568
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.text, fontWeight: "600" }, numberOfLines: 1, children: (r.summary ?? r.description ?? "Report").slice(0, 60) }),
|
|
532
569
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: { color: colors.sub, fontSize: 11 }, children: r.status })
|
|
533
|
-
] }, r.id)) }) :
|
|
570
|
+
] }, r.id)) }) : sheetTab === "community" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: [
|
|
571
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.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." }),
|
|
572
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
573
|
+
import_react_native2.TextInput,
|
|
574
|
+
{
|
|
575
|
+
style: [s.input, { backgroundColor: colors.card, color: colors.text, borderColor: colors.border, minHeight: 48 }],
|
|
576
|
+
placeholder: "you@example.com",
|
|
577
|
+
placeholderTextColor: colors.sub,
|
|
578
|
+
keyboardType: "email-address",
|
|
579
|
+
autoCapitalize: "none",
|
|
580
|
+
autoCorrect: false,
|
|
581
|
+
textContentType: "emailAddress"
|
|
582
|
+
}
|
|
583
|
+
),
|
|
584
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: colors.accentInk }], children: "Send sign-in link \u2192" }) }),
|
|
585
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.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." })
|
|
586
|
+
] }) : phase === "sent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.sentWrap, children: [
|
|
534
587
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentEmoji], children: "\u2705" }),
|
|
535
588
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentText, { color: colors.text }], children: "Report sent!" })
|
|
536
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
589
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
590
|
+
import_react_native2.ScrollView,
|
|
591
|
+
{
|
|
592
|
+
style: s.body,
|
|
593
|
+
keyboardShouldPersistTaps: "handled",
|
|
594
|
+
contentContainerStyle: { paddingBottom: 24 },
|
|
595
|
+
children: [
|
|
596
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.stepLabel, { color: colors.sub }], children: "What kind of issue?" }),
|
|
597
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.catRow, children: CATEGORIES.map((c) => {
|
|
598
|
+
const active = category === c.key;
|
|
599
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
600
|
+
import_react_native2.TouchableOpacity,
|
|
547
601
|
{
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
602
|
+
onPress: () => setCategory(c.key),
|
|
603
|
+
activeOpacity: 0.7,
|
|
604
|
+
style: [
|
|
605
|
+
s.catBtn,
|
|
606
|
+
{
|
|
607
|
+
backgroundColor: active ? colors.accent : colors.card,
|
|
608
|
+
borderColor: active ? colors.accent : colors.border
|
|
609
|
+
}
|
|
610
|
+
],
|
|
611
|
+
children: [
|
|
612
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.catEmoji, children: c.emoji }),
|
|
613
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
614
|
+
import_react_native2.Text,
|
|
615
|
+
{
|
|
616
|
+
style: [
|
|
617
|
+
s.catLabel,
|
|
618
|
+
{ color: active ? colors.accentInk : colors.text }
|
|
619
|
+
],
|
|
620
|
+
children: c.label
|
|
621
|
+
}
|
|
622
|
+
)
|
|
623
|
+
]
|
|
624
|
+
},
|
|
625
|
+
c.key
|
|
626
|
+
);
|
|
627
|
+
}) }),
|
|
628
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
629
|
+
import_react_native2.TextInput,
|
|
630
|
+
{
|
|
631
|
+
style: [
|
|
632
|
+
s.input,
|
|
556
633
|
{
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
],
|
|
561
|
-
children: c.label
|
|
634
|
+
backgroundColor: colors.card,
|
|
635
|
+
color: colors.text,
|
|
636
|
+
borderColor: colors.border
|
|
562
637
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
638
|
+
],
|
|
639
|
+
placeholder: "What happened?",
|
|
640
|
+
placeholderTextColor: colors.sub,
|
|
641
|
+
multiline: true,
|
|
642
|
+
textAlignVertical: "top",
|
|
643
|
+
value: description,
|
|
644
|
+
onChangeText: setDescription,
|
|
645
|
+
editable: phase === "form"
|
|
646
|
+
}
|
|
647
|
+
),
|
|
648
|
+
activeScreenshot ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotRow, children: [
|
|
649
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
650
|
+
import_react_native2.Image,
|
|
651
|
+
{
|
|
652
|
+
source: { uri: activeScreenshot },
|
|
653
|
+
style: s.screenshotThumb,
|
|
654
|
+
accessibilityLabel: "Attached screenshot"
|
|
655
|
+
}
|
|
656
|
+
),
|
|
657
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotMeta, children: [
|
|
658
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotLabel, { color: colors.text }], children: "Screenshot attached" }),
|
|
659
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotSub, { color: colors.sub }], children: "Helps the team see the issue" })
|
|
660
|
+
] }),
|
|
661
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
662
|
+
import_react_native2.TouchableOpacity,
|
|
663
|
+
{
|
|
664
|
+
onPress: () => {
|
|
665
|
+
setScreenshotAttached(false);
|
|
666
|
+
onClearScreenshot?.();
|
|
667
|
+
},
|
|
668
|
+
hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
|
|
669
|
+
accessibilityLabel: "Remove screenshot",
|
|
670
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotRemove, { color: colors.sub }], children: "\u2715" })
|
|
671
|
+
}
|
|
672
|
+
)
|
|
673
|
+
] }) : null,
|
|
674
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
675
|
+
import_react_native2.TouchableOpacity,
|
|
574
676
|
{
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
677
|
+
onPress: handleSubmit,
|
|
678
|
+
disabled: !canSubmit,
|
|
679
|
+
activeOpacity: 0.8,
|
|
680
|
+
style: [
|
|
681
|
+
s.submitBtn,
|
|
682
|
+
{ backgroundColor: canSubmit ? colors.accent : colors.disabled }
|
|
683
|
+
],
|
|
684
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.submitText, { color: canSubmit ? colors.accentInk : colors.disabledText }], children: phase === "sending" ? "Sending\u2026" : "Submit report" })
|
|
578
685
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
textAlignVertical: "top",
|
|
584
|
-
value: description,
|
|
585
|
-
onChangeText: setDescription,
|
|
586
|
-
editable: phase === "form"
|
|
587
|
-
}
|
|
588
|
-
),
|
|
589
|
-
activeScreenshot ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotRow, children: [
|
|
590
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
591
|
-
import_react_native2.Image,
|
|
592
|
-
{
|
|
593
|
-
source: { uri: activeScreenshot },
|
|
594
|
-
style: s.screenshotThumb,
|
|
595
|
-
accessibilityLabel: "Attached screenshot"
|
|
596
|
-
}
|
|
597
|
-
),
|
|
598
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.screenshotMeta, children: [
|
|
599
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotLabel, { color: colors.text }], children: "Screenshot attached" }),
|
|
600
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotSub, { color: colors.sub }], children: "Helps the team see the issue" })
|
|
601
|
-
] }),
|
|
602
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
603
|
-
import_react_native2.TouchableOpacity,
|
|
604
|
-
{
|
|
605
|
-
onPress: () => {
|
|
606
|
-
setScreenshotAttached(false);
|
|
607
|
-
onClearScreenshot?.();
|
|
608
|
-
},
|
|
609
|
-
hitSlop: { top: 8, bottom: 8, left: 8, right: 8 },
|
|
610
|
-
accessibilityLabel: "Remove screenshot",
|
|
611
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.screenshotRemove, { color: colors.sub }], children: "\u2715" })
|
|
612
|
-
}
|
|
613
|
-
)
|
|
614
|
-
] }) : null,
|
|
615
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
616
|
-
import_react_native2.TouchableOpacity,
|
|
617
|
-
{
|
|
618
|
-
onPress: handleSubmit,
|
|
619
|
-
disabled: !canSubmit,
|
|
620
|
-
activeOpacity: 0.8,
|
|
621
|
-
style: [
|
|
622
|
-
s.submitBtn,
|
|
623
|
-
{ backgroundColor: canSubmit ? colors.accent : colors.border }
|
|
624
|
-
],
|
|
625
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.submitText, children: phase === "sending" ? "Sending\u2026" : "Submit" })
|
|
626
|
-
}
|
|
627
|
-
)
|
|
628
|
-
] })
|
|
686
|
+
)
|
|
687
|
+
]
|
|
688
|
+
}
|
|
689
|
+
)
|
|
629
690
|
]
|
|
630
691
|
}
|
|
631
692
|
)
|
|
@@ -663,6 +724,24 @@ var s = import_react_native2.StyleSheet.create({
|
|
|
663
724
|
borderRadius: 3,
|
|
664
725
|
opacity: 0.5
|
|
665
726
|
},
|
|
727
|
+
brandHeader: {
|
|
728
|
+
paddingHorizontal: 20,
|
|
729
|
+
paddingTop: 10,
|
|
730
|
+
paddingBottom: 12,
|
|
731
|
+
borderBottomWidth: 1.5
|
|
732
|
+
},
|
|
733
|
+
brandEyebrow: {
|
|
734
|
+
fontSize: 10,
|
|
735
|
+
fontWeight: "700",
|
|
736
|
+
letterSpacing: 1.2,
|
|
737
|
+
marginBottom: 2,
|
|
738
|
+
opacity: 0.7
|
|
739
|
+
},
|
|
740
|
+
brandTitle: {
|
|
741
|
+
fontSize: 18,
|
|
742
|
+
fontWeight: "800",
|
|
743
|
+
letterSpacing: -0.2
|
|
744
|
+
},
|
|
666
745
|
body: {
|
|
667
746
|
paddingHorizontal: 20,
|
|
668
747
|
paddingTop: 4
|
|
@@ -672,6 +751,14 @@ var s = import_react_native2.StyleSheet.create({
|
|
|
672
751
|
fontWeight: "700",
|
|
673
752
|
marginBottom: 16
|
|
674
753
|
},
|
|
754
|
+
stepLabel: {
|
|
755
|
+
fontSize: 12,
|
|
756
|
+
fontWeight: "600",
|
|
757
|
+
letterSpacing: 0.2,
|
|
758
|
+
marginTop: 8,
|
|
759
|
+
marginBottom: 10,
|
|
760
|
+
textTransform: "uppercase"
|
|
761
|
+
},
|
|
675
762
|
catRow: {
|
|
676
763
|
flexDirection: "row",
|
|
677
764
|
justifyContent: "space-between",
|
|
@@ -800,16 +887,135 @@ var s = import_react_native2.StyleSheet.create({
|
|
|
800
887
|
// src/components/MushiFloatingButton.tsx
|
|
801
888
|
var import_react2 = require("react");
|
|
802
889
|
var import_react_native3 = require("react-native");
|
|
890
|
+
var import_core2 = require("@mushi-mushi/core");
|
|
803
891
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
892
|
+
var FAB_SIZE = 56;
|
|
893
|
+
var EDGE_PADDING = 12;
|
|
894
|
+
function getScreenBounds() {
|
|
895
|
+
const { width, height } = import_react_native3.Dimensions.get("window");
|
|
896
|
+
return { width, height };
|
|
897
|
+
}
|
|
898
|
+
function clamp(value, min, max) {
|
|
899
|
+
return Math.min(Math.max(value, min), max);
|
|
900
|
+
}
|
|
901
|
+
var STORAGE_KEY = "mushi:fab-pos";
|
|
902
|
+
async function loadStoredPos() {
|
|
903
|
+
try {
|
|
904
|
+
const AsyncStorage = (await import("@react-native-async-storage/async-storage")).default;
|
|
905
|
+
const raw = await AsyncStorage.getItem(STORAGE_KEY);
|
|
906
|
+
if (!raw) return null;
|
|
907
|
+
return JSON.parse(raw);
|
|
908
|
+
} catch {
|
|
909
|
+
return null;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
async function storePos(pos) {
|
|
913
|
+
try {
|
|
914
|
+
const AsyncStorage = (await import("@react-native-async-storage/async-storage")).default;
|
|
915
|
+
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(pos));
|
|
916
|
+
} catch {
|
|
917
|
+
}
|
|
918
|
+
}
|
|
804
919
|
var MushiFloatingButton = ({
|
|
805
920
|
onPress,
|
|
806
921
|
position = "bottom-right",
|
|
807
|
-
inset
|
|
922
|
+
inset,
|
|
923
|
+
config,
|
|
924
|
+
accent
|
|
808
925
|
}) => {
|
|
809
926
|
const scheme = (0, import_react_native3.useColorScheme)();
|
|
810
927
|
const dark = scheme === "dark";
|
|
928
|
+
const draggable = config?.draggable ?? false;
|
|
929
|
+
const snapToEdge = config?.snapToEdge ?? true;
|
|
930
|
+
const persist = config?.persist ?? false;
|
|
931
|
+
const baseBottom = inset?.bottom ?? (import_react_native3.Platform.OS === "ios" ? 50 : 28);
|
|
932
|
+
const baseX = position === "bottom-left" ? inset?.left ?? EDGE_PADDING : (() => {
|
|
933
|
+
const { width } = getScreenBounds();
|
|
934
|
+
return width - FAB_SIZE - (inset?.right ?? EDGE_PADDING);
|
|
935
|
+
})();
|
|
811
936
|
const scale = (0, import_react2.useRef)(new import_react_native3.Animated.Value(1)).current;
|
|
937
|
+
const posX = (0, import_react2.useRef)(new import_react_native3.Animated.Value(baseX)).current;
|
|
938
|
+
const posY = (0, import_react2.useRef)(new import_react_native3.Animated.Value(0)).current;
|
|
939
|
+
const currentX = (0, import_react2.useRef)(baseX);
|
|
940
|
+
const currentY = (0, import_react2.useRef)(0);
|
|
941
|
+
const isDragging = (0, import_react2.useRef)(false);
|
|
942
|
+
const accentColor = accent ?? (0, import_core2.mushiPalette)(dark ? "dark" : "light").accent;
|
|
943
|
+
(0, import_react2.useEffect)(() => {
|
|
944
|
+
if (!persist) return;
|
|
945
|
+
void loadStoredPos().then((pos) => {
|
|
946
|
+
if (!pos) return;
|
|
947
|
+
currentX.current = pos.x;
|
|
948
|
+
currentY.current = pos.y;
|
|
949
|
+
posX.setValue(pos.x);
|
|
950
|
+
posY.setValue(pos.y);
|
|
951
|
+
});
|
|
952
|
+
}, [persist, posX, posY]);
|
|
953
|
+
const snapAndPersist = (0, import_react2.useCallback)(
|
|
954
|
+
(x, y) => {
|
|
955
|
+
const { width, height } = getScreenBounds();
|
|
956
|
+
let snappedX = x;
|
|
957
|
+
if (snapToEdge) {
|
|
958
|
+
const midX = width / 2;
|
|
959
|
+
snappedX = x + FAB_SIZE / 2 < midX ? EDGE_PADDING : width - FAB_SIZE - EDGE_PADDING;
|
|
960
|
+
}
|
|
961
|
+
const clampedX = clamp(snappedX, EDGE_PADDING, width - FAB_SIZE - EDGE_PADDING);
|
|
962
|
+
const clampedY = clamp(y, -(height - FAB_SIZE - baseBottom - 60), 0);
|
|
963
|
+
currentX.current = clampedX;
|
|
964
|
+
currentY.current = clampedY;
|
|
965
|
+
import_react_native3.Animated.spring(posX, {
|
|
966
|
+
toValue: clampedX,
|
|
967
|
+
useNativeDriver: false,
|
|
968
|
+
damping: 18,
|
|
969
|
+
stiffness: 220
|
|
970
|
+
}).start();
|
|
971
|
+
import_react_native3.Animated.spring(posY, {
|
|
972
|
+
toValue: clampedY,
|
|
973
|
+
useNativeDriver: false,
|
|
974
|
+
damping: 18,
|
|
975
|
+
stiffness: 220
|
|
976
|
+
}).start();
|
|
977
|
+
if (persist) void storePos({ x: clampedX, y: clampedY });
|
|
978
|
+
},
|
|
979
|
+
[snapToEdge, persist, posX, posY, baseBottom]
|
|
980
|
+
);
|
|
981
|
+
const panResponder = (0, import_react2.useRef)(
|
|
982
|
+
draggable ? import_react_native3.PanResponder.create({
|
|
983
|
+
onStartShouldSetPanResponder: () => true,
|
|
984
|
+
onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dx) > 6 || Math.abs(g.dy) > 6,
|
|
985
|
+
onPanResponderGrant: () => {
|
|
986
|
+
isDragging.current = false;
|
|
987
|
+
posX.stopAnimation();
|
|
988
|
+
posY.stopAnimation();
|
|
989
|
+
},
|
|
990
|
+
onPanResponderMove: (_, g) => {
|
|
991
|
+
if (Math.abs(g.dx) > 6 || Math.abs(g.dy) > 6) {
|
|
992
|
+
isDragging.current = true;
|
|
993
|
+
scale.setValue(1);
|
|
994
|
+
}
|
|
995
|
+
const { width, height } = getScreenBounds();
|
|
996
|
+
const newX = clamp(
|
|
997
|
+
currentX.current + g.dx,
|
|
998
|
+
EDGE_PADDING,
|
|
999
|
+
width - FAB_SIZE - EDGE_PADDING
|
|
1000
|
+
);
|
|
1001
|
+
const newY = clamp(
|
|
1002
|
+
currentY.current + g.dy,
|
|
1003
|
+
-(height - FAB_SIZE - baseBottom - 60),
|
|
1004
|
+
0
|
|
1005
|
+
);
|
|
1006
|
+
posX.setValue(newX);
|
|
1007
|
+
posY.setValue(newY);
|
|
1008
|
+
},
|
|
1009
|
+
onPanResponderRelease: (_, g) => {
|
|
1010
|
+
if (isDragging.current) {
|
|
1011
|
+
snapAndPersist(currentX.current + g.dx, currentY.current + g.dy);
|
|
1012
|
+
}
|
|
1013
|
+
isDragging.current = false;
|
|
1014
|
+
}
|
|
1015
|
+
}) : import_react_native3.PanResponder.create({})
|
|
1016
|
+
).current;
|
|
812
1017
|
const handlePressIn = (0, import_react2.useCallback)(() => {
|
|
1018
|
+
if (isDragging.current) return;
|
|
813
1019
|
import_react_native3.Animated.spring(scale, {
|
|
814
1020
|
toValue: 0.85,
|
|
815
1021
|
useNativeDriver: true,
|
|
@@ -824,39 +1030,35 @@ var MushiFloatingButton = ({
|
|
|
824
1030
|
damping: 10,
|
|
825
1031
|
stiffness: 250
|
|
826
1032
|
}).start();
|
|
827
|
-
|
|
828
|
-
|
|
1033
|
+
if (!isDragging.current) onPress();
|
|
1034
|
+
}, [scale, onPress]);
|
|
829
1035
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
830
|
-
import_react_native3.
|
|
1036
|
+
import_react_native3.Animated.View,
|
|
831
1037
|
{
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
836
|
-
import_react_native3.Animated.View,
|
|
1038
|
+
...draggable ? panResponder.panHandlers : {},
|
|
1039
|
+
style: [
|
|
1040
|
+
s2.fab,
|
|
837
1041
|
{
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
backgroundColor: dark ? "#0a84ff" : "#007aff",
|
|
844
|
-
transform: [{ scale }],
|
|
845
|
-
shadowColor: dark ? "#0a84ff" : "#000"
|
|
846
|
-
}
|
|
847
|
-
],
|
|
848
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Animated.Text, { style: s2.emoji, children: "\u{1F41B}" })
|
|
1042
|
+
left: posX,
|
|
1043
|
+
bottom: import_react_native3.Animated.add(new import_react_native3.Animated.Value(baseBottom), import_react_native3.Animated.multiply(posY, -1)),
|
|
1044
|
+
backgroundColor: accentColor,
|
|
1045
|
+
shadowColor: dark ? accentColor : "#000",
|
|
1046
|
+
transform: [{ scale }]
|
|
849
1047
|
}
|
|
850
|
-
|
|
1048
|
+
],
|
|
1049
|
+
onStartShouldSetResponder: () => !draggable,
|
|
1050
|
+
onResponderGrant: handlePressIn,
|
|
1051
|
+
onResponderRelease: handlePressOut,
|
|
1052
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Animated.Text, { style: s2.emoji, children: import_core2.MUSHI_COPY.triggerText })
|
|
851
1053
|
}
|
|
852
1054
|
);
|
|
853
1055
|
};
|
|
854
1056
|
var s2 = import_react_native3.StyleSheet.create({
|
|
855
1057
|
fab: {
|
|
856
1058
|
position: "absolute",
|
|
857
|
-
width:
|
|
858
|
-
height:
|
|
859
|
-
borderRadius:
|
|
1059
|
+
width: FAB_SIZE,
|
|
1060
|
+
height: FAB_SIZE,
|
|
1061
|
+
borderRadius: FAB_SIZE / 2,
|
|
860
1062
|
alignItems: "center",
|
|
861
1063
|
justifyContent: "center",
|
|
862
1064
|
zIndex: 9999,
|
|
@@ -870,14 +1072,53 @@ var s2 = import_react_native3.StyleSheet.create({
|
|
|
870
1072
|
}
|
|
871
1073
|
});
|
|
872
1074
|
|
|
1075
|
+
// src/version.ts
|
|
1076
|
+
var MUSHI_SDK_PACKAGE = "@mushi-mushi/react-native";
|
|
1077
|
+
var MUSHI_SDK_VERSION = "0.17.0" ? "0.17.0" : "0.17.0";
|
|
1078
|
+
|
|
873
1079
|
// src/provider.tsx
|
|
874
1080
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
875
1081
|
var MushiContext = (0, import_react3.createContext)(null);
|
|
876
|
-
function
|
|
1082
|
+
function breadcrumbToTimelineKind(category) {
|
|
1083
|
+
switch (category) {
|
|
1084
|
+
case "navigation":
|
|
1085
|
+
return "route";
|
|
1086
|
+
case "ui.click":
|
|
1087
|
+
case "ui.tap":
|
|
1088
|
+
return "click";
|
|
1089
|
+
case "xhr":
|
|
1090
|
+
case "fetch":
|
|
1091
|
+
case "network":
|
|
1092
|
+
return "request";
|
|
1093
|
+
default:
|
|
1094
|
+
return "log";
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
1098
|
+
const proc = globalThis.process;
|
|
1099
|
+
const envProjectId = proc?.env?.MUSHI_PROJECT_ID || proc?.env?.EXPO_PUBLIC_MUSHI_PROJECT_ID;
|
|
1100
|
+
const envApiKey = proc?.env?.MUSHI_API_KEY || proc?.env?.EXPO_PUBLIC_MUSHI_API_KEY;
|
|
1101
|
+
const config = {
|
|
1102
|
+
...envProjectId ? { projectId: envProjectId } : {},
|
|
1103
|
+
...envApiKey ? { apiKey: envApiKey } : {},
|
|
1104
|
+
...configProp,
|
|
1105
|
+
...barePropConfig
|
|
1106
|
+
};
|
|
1107
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
1108
|
+
if (!config.projectId || !config.apiKey) {
|
|
1109
|
+
console.warn(
|
|
1110
|
+
"[MushiProvider] Missing projectId or apiKey \u2014 set MUSHI_PROJECT_ID / MUSHI_API_KEY (or EXPO_PUBLIC_* equivalents) or pass them as props."
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
877
1114
|
const consoleRef = (0, import_react3.useRef)(null);
|
|
878
1115
|
const networkRef = (0, import_react3.useRef)(null);
|
|
879
1116
|
const queueRef = (0, import_react3.useRef)(null);
|
|
880
1117
|
const apiClientRef = (0, import_react3.useRef)(null);
|
|
1118
|
+
const sessionIdRef = (0, import_react3.useRef)(`sess_${(0, import_core3.newUuid)()}`);
|
|
1119
|
+
const breadcrumbsRef = (0, import_react3.useRef)(
|
|
1120
|
+
(0, import_core3.createBreadcrumbBuffer)({ max: config.capture?.maxBreadcrumbs ?? 50 })
|
|
1121
|
+
);
|
|
881
1122
|
const [sheetVisible, setSheetVisible] = (0, import_react3.useState)(false);
|
|
882
1123
|
const [sheetScreenshot, setSheetScreenshot] = (0, import_react3.useState)(null);
|
|
883
1124
|
const reporterTokenRef = (0, import_react3.useRef)(`rn-${config.projectId}-anon`);
|
|
@@ -888,6 +1129,8 @@ function MushiProvider({ children, ...config }) {
|
|
|
888
1129
|
resolveReporterTokenReady = resolve;
|
|
889
1130
|
})
|
|
890
1131
|
);
|
|
1132
|
+
const userTokenRef = (0, import_react3.useRef)(null);
|
|
1133
|
+
const pageContextRef = (0, import_react3.useRef)(null);
|
|
891
1134
|
const rawEndpoint = config.apiEndpoint ?? config.endpoint;
|
|
892
1135
|
if (rawEndpoint !== void 0 && rawEndpoint.trim() === "") {
|
|
893
1136
|
throw new Error(
|
|
@@ -897,7 +1140,7 @@ function MushiProvider({ children, ...config }) {
|
|
|
897
1140
|
if (config.endpoint && !config.apiEndpoint && __DEV__) {
|
|
898
1141
|
console.warn("[MushiProvider] `endpoint` is deprecated \u2014 use `apiEndpoint` instead.");
|
|
899
1142
|
}
|
|
900
|
-
const apiEndpoint = rawEndpoint ??
|
|
1143
|
+
const apiEndpoint = rawEndpoint ?? import_core3.DEFAULT_API_ENDPOINT;
|
|
901
1144
|
(0, import_react3.useEffect)(() => {
|
|
902
1145
|
if (config.capture?.console !== false) {
|
|
903
1146
|
consoleRef.current = setupConsoleCapture(config.capture?.maxConsoleEntries);
|
|
@@ -905,10 +1148,11 @@ function MushiProvider({ children, ...config }) {
|
|
|
905
1148
|
if (config.capture?.network !== false) {
|
|
906
1149
|
networkRef.current = setupNetworkCapture(config.capture?.maxNetworkEntries, apiEndpoint);
|
|
907
1150
|
}
|
|
908
|
-
apiClientRef.current = (0,
|
|
1151
|
+
apiClientRef.current = (0, import_core3.createApiClient)({
|
|
909
1152
|
projectId: config.projectId,
|
|
910
1153
|
apiKey: config.apiKey,
|
|
911
|
-
apiEndpoint
|
|
1154
|
+
apiEndpoint,
|
|
1155
|
+
getUserToken: () => userTokenRef.current
|
|
912
1156
|
});
|
|
913
1157
|
queueRef.current = new AsyncStorageQueue({
|
|
914
1158
|
maxSize: config.storage?.maxQueueSize,
|
|
@@ -921,6 +1165,12 @@ function MushiProvider({ children, ...config }) {
|
|
|
921
1165
|
});
|
|
922
1166
|
queueRef.current.flush().catch(() => {
|
|
923
1167
|
});
|
|
1168
|
+
breadcrumbsRef.current.add({
|
|
1169
|
+
category: "lifecycle",
|
|
1170
|
+
level: "info",
|
|
1171
|
+
message: "Mushi SDK initialised",
|
|
1172
|
+
data: { sdk: MUSHI_SDK_PACKAGE, version: MUSHI_SDK_VERSION }
|
|
1173
|
+
});
|
|
924
1174
|
(async () => {
|
|
925
1175
|
const TOKEN_KEY = "@mushi:reporter_token";
|
|
926
1176
|
try {
|
|
@@ -929,14 +1179,14 @@ function MushiProvider({ children, ...config }) {
|
|
|
929
1179
|
if (existing && existing.startsWith("mushi_")) {
|
|
930
1180
|
reporterTokenRef.current = existing;
|
|
931
1181
|
} else {
|
|
932
|
-
const fresh = `mushi_${(0,
|
|
1182
|
+
const fresh = `mushi_${(0, import_core3.newUuid)()}`;
|
|
933
1183
|
reporterTokenRef.current = fresh;
|
|
934
1184
|
await AsyncStorage.setItem(TOKEN_KEY, fresh);
|
|
935
1185
|
}
|
|
936
1186
|
resolveReporterTokenReady();
|
|
937
1187
|
} catch {
|
|
938
1188
|
if (reporterTokenRef.current === `rn-${config.projectId}-anon`) {
|
|
939
|
-
reporterTokenRef.current = `mushi_${(0,
|
|
1189
|
+
reporterTokenRef.current = `mushi_${(0, import_core3.newUuid)()}`;
|
|
940
1190
|
}
|
|
941
1191
|
resolveReporterTokenReady();
|
|
942
1192
|
}
|
|
@@ -962,6 +1212,17 @@ function MushiProvider({ children, ...config }) {
|
|
|
962
1212
|
return () => unsubscribe?.();
|
|
963
1213
|
}, []);
|
|
964
1214
|
const open = (0, import_react3.useCallback)(() => {
|
|
1215
|
+
breadcrumbsRef.current.add({
|
|
1216
|
+
category: "lifecycle",
|
|
1217
|
+
level: "info",
|
|
1218
|
+
message: "Widget opened",
|
|
1219
|
+
...screenRef.current?.name ? { data: { screen: screenRef.current.name } } : {}
|
|
1220
|
+
});
|
|
1221
|
+
if (config.capture?.screenshot === false) {
|
|
1222
|
+
setSheetScreenshot(null);
|
|
1223
|
+
setSheetVisible(true);
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
965
1226
|
let vshot = null;
|
|
966
1227
|
try {
|
|
967
1228
|
vshot = require("react-native-view-shot");
|
|
@@ -979,7 +1240,7 @@ function MushiProvider({ children, ...config }) {
|
|
|
979
1240
|
}).finally(() => {
|
|
980
1241
|
setSheetVisible(true);
|
|
981
1242
|
});
|
|
982
|
-
}, []);
|
|
1243
|
+
}, [config.capture?.screenshot]);
|
|
983
1244
|
const close = (0, import_react3.useCallback)(() => setSheetVisible(false), []);
|
|
984
1245
|
const attachTo = (0, import_react3.useCallback)(() => ({ onPress: open }), [open]);
|
|
985
1246
|
(0, import_react3.useEffect)(() => {
|
|
@@ -1013,16 +1274,37 @@ function MushiProvider({ children, ...config }) {
|
|
|
1013
1274
|
const submitReport = (0, import_react3.useCallback)(
|
|
1014
1275
|
async (data) => {
|
|
1015
1276
|
const deviceInfo = getDeviceInfo();
|
|
1277
|
+
breadcrumbsRef.current.add({
|
|
1278
|
+
category: "lifecycle",
|
|
1279
|
+
level: "info",
|
|
1280
|
+
message: "Report submitted",
|
|
1281
|
+
data: { category: data.category }
|
|
1282
|
+
});
|
|
1016
1283
|
const contextMeta = {};
|
|
1017
|
-
if (userRef.current?.id)
|
|
1284
|
+
if (userRef.current?.id) {
|
|
1285
|
+
contextMeta.user = {
|
|
1286
|
+
id: userRef.current.id,
|
|
1287
|
+
...userRef.current.email ? { email: userRef.current.email } : {},
|
|
1288
|
+
...userRef.current.name ? { name: userRef.current.name } : {},
|
|
1289
|
+
...userRef.current.provider ? { provider: userRef.current.provider } : {}
|
|
1290
|
+
};
|
|
1291
|
+
contextMeta.userId = userRef.current.id;
|
|
1292
|
+
}
|
|
1018
1293
|
if (userRef.current?.email) contextMeta.userEmail = userRef.current.email;
|
|
1019
1294
|
if (userRef.current?.name) contextMeta.userName = userRef.current.name;
|
|
1020
1295
|
if (screenRef.current?.name) contextMeta.screenName = screenRef.current.name;
|
|
1021
1296
|
if (screenRef.current?.route) contextMeta.screenRoute = screenRef.current.route;
|
|
1022
1297
|
if (screenRef.current?.feature) contextMeta.screenFeature = screenRef.current.feature;
|
|
1023
1298
|
const merged = Object.keys(metadataRef.current).length > 0 ? { ...contextMeta, ...metadataRef.current } : Object.keys(contextMeta).length > 0 ? contextMeta : void 0;
|
|
1299
|
+
const breadcrumbs = breadcrumbsRef.current.getAll();
|
|
1300
|
+
const timeline = breadcrumbs.map((c) => ({
|
|
1301
|
+
ts: c.timestamp,
|
|
1302
|
+
kind: breadcrumbToTimelineKind(c.category),
|
|
1303
|
+
payload: { message: c.message, level: c.level, ...c.data ?? {} }
|
|
1304
|
+
}));
|
|
1305
|
+
const fingerprintHash = getDeviceFingerprintHash(deviceInfo);
|
|
1024
1306
|
const report = {
|
|
1025
|
-
id: (0,
|
|
1307
|
+
id: (0, import_core3.newUuid)(),
|
|
1026
1308
|
projectId: config.projectId,
|
|
1027
1309
|
category: data.category,
|
|
1028
1310
|
description: data.description,
|
|
@@ -1043,7 +1325,14 @@ function MushiProvider({ children, ...config }) {
|
|
|
1043
1325
|
})) ?? [],
|
|
1044
1326
|
screenshotDataUrl: data.screenshotDataUrl,
|
|
1045
1327
|
metadata: merged,
|
|
1328
|
+
sessionId: sessionIdRef.current,
|
|
1046
1329
|
reporterToken: reporterTokenRef.current,
|
|
1330
|
+
...fingerprintHash ? { fingerprintHash } : {},
|
|
1331
|
+
...deviceInfo.appVersion ? { appVersion: deviceInfo.appVersion } : {},
|
|
1332
|
+
sdkPackage: MUSHI_SDK_PACKAGE,
|
|
1333
|
+
sdkVersion: MUSHI_SDK_VERSION,
|
|
1334
|
+
...breadcrumbs.length > 0 ? { breadcrumbs } : {},
|
|
1335
|
+
...timeline.length > 0 ? { timeline } : {},
|
|
1047
1336
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1048
1337
|
};
|
|
1049
1338
|
const client = apiClientRef.current;
|
|
@@ -1101,11 +1390,53 @@ function MushiProvider({ children, ...config }) {
|
|
|
1101
1390
|
provider: traits?.provider
|
|
1102
1391
|
};
|
|
1103
1392
|
},
|
|
1393
|
+
setUser(user) {
|
|
1394
|
+
userRef.current = {
|
|
1395
|
+
id: user.id,
|
|
1396
|
+
email: user.email,
|
|
1397
|
+
name: user.name,
|
|
1398
|
+
provider: user.provider
|
|
1399
|
+
};
|
|
1400
|
+
},
|
|
1401
|
+
identifyWithToken(token) {
|
|
1402
|
+
userTokenRef.current = token && typeof token === "string" ? token : null;
|
|
1403
|
+
if (userTokenRef.current) {
|
|
1404
|
+
const claims = (0, import_core3.parseIdentityToken)(userTokenRef.current);
|
|
1405
|
+
if (claims?.sub) {
|
|
1406
|
+
userRef.current = {
|
|
1407
|
+
id: claims.sub,
|
|
1408
|
+
...claims.email ? { email: claims.email } : {},
|
|
1409
|
+
...claims.name ? { name: claims.name } : {}
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
},
|
|
1414
|
+
publishPageContext(context) {
|
|
1415
|
+
pageContextRef.current = context && context.route ? context : null;
|
|
1416
|
+
},
|
|
1104
1417
|
setMetadata(key, value) {
|
|
1105
1418
|
metadataRef.current[key] = value;
|
|
1106
1419
|
},
|
|
1107
1420
|
setScreen(screen) {
|
|
1421
|
+
const prev = screenRef.current?.name;
|
|
1108
1422
|
screenRef.current = screen;
|
|
1423
|
+
if (screen.name && screen.name !== prev) {
|
|
1424
|
+
breadcrumbsRef.current.add({
|
|
1425
|
+
category: "navigation",
|
|
1426
|
+
level: "info",
|
|
1427
|
+
message: `Navigated to ${screen.name}`,
|
|
1428
|
+
// Always include `route` (fallback to the screen name) so the
|
|
1429
|
+
// admin repro-timeline renders the destination instead of
|
|
1430
|
+
// "unknown route" when the host only set a screen name.
|
|
1431
|
+
data: {
|
|
1432
|
+
route: screen.route ?? screen.name,
|
|
1433
|
+
...screen.feature ? { feature: screen.feature } : {}
|
|
1434
|
+
}
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
},
|
|
1438
|
+
addBreadcrumb(crumb) {
|
|
1439
|
+
breadcrumbsRef.current.add(crumb);
|
|
1109
1440
|
},
|
|
1110
1441
|
// Reporter API — scoped to the device's persistent reporter token
|
|
1111
1442
|
async listMyReports() {
|