@mushi-mushi/react-native 0.16.0 → 0.18.1
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 +951 -0
- package/CONTRIBUTING.md +50 -9
- package/README.md +36 -1
- package/dist/index.cjs +221 -22
- package/dist/index.d.cts +40 -4
- package/dist/index.d.ts +40 -4
- package/dist/index.js +218 -17
- package/package.json +8 -7
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
1
|
import * as react from 'react';
|
|
3
2
|
import { ReactNode, FC, ReactElement } from 'react';
|
|
4
|
-
import { MushiRewardsConfig, MushiReporterReport, MushiReporterComment, MushiHallOfFameEntry, MushiTierResult, MushiReputationResult } from '@mushi-mushi/core';
|
|
3
|
+
import { MushiRewardsConfig, MushiPageContext, MushiBreadcrumb, MushiReporterReport, MushiReporterComment, MushiHallOfFameEntry, MushiTierResult, MushiReputationResult } from '@mushi-mushi/core';
|
|
5
4
|
export { MushiHallOfFameEntry, MushiReporterComment, MushiReporterReport } from '@mushi-mushi/core';
|
|
6
5
|
|
|
7
6
|
interface ConsoleEntry {
|
|
@@ -72,8 +71,19 @@ interface MushiRNConfig {
|
|
|
72
71
|
console?: boolean;
|
|
73
72
|
network?: boolean;
|
|
74
73
|
navigation?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Capture a screenshot of the current screen when the widget opens.
|
|
76
|
+
* Defaults to `true`. Requires the optional `react-native-view-shot`
|
|
77
|
+
* peer dependency — when it isn't installed, capture is skipped silently
|
|
78
|
+
* (the report is still sent without an image). The user always sees a
|
|
79
|
+
* thumbnail in the sheet and can remove it before submitting, so
|
|
80
|
+
* sensitive screens (balances, PII) are never sent without consent.
|
|
81
|
+
*/
|
|
82
|
+
screenshot?: boolean;
|
|
75
83
|
maxConsoleEntries?: number;
|
|
76
84
|
maxNetworkEntries?: number;
|
|
85
|
+
/** Hard cap on the breadcrumb / repro-timeline ring buffer. Default 50. */
|
|
86
|
+
maxBreadcrumbs?: number;
|
|
77
87
|
};
|
|
78
88
|
storage?: {
|
|
79
89
|
maxQueueSize?: number;
|
|
@@ -104,6 +114,27 @@ interface MushiRNInstance {
|
|
|
104
114
|
provider?: string;
|
|
105
115
|
[k: string]: unknown;
|
|
106
116
|
}): void;
|
|
117
|
+
/**
|
|
118
|
+
* Alias for {@link identify} matching the web SDK's `Mushi.setUser()`.
|
|
119
|
+
* Accepts an object so hosts can call `setUser({ id, email, name })`.
|
|
120
|
+
*/
|
|
121
|
+
setUser(user: {
|
|
122
|
+
id: string;
|
|
123
|
+
email?: string;
|
|
124
|
+
name?: string;
|
|
125
|
+
provider?: string;
|
|
126
|
+
}): void;
|
|
127
|
+
/**
|
|
128
|
+
* Identify the current end user with a signed Mushi identity JWT minted by
|
|
129
|
+
* the host server. Mirrors the web SDK; the backend verifies the signature
|
|
130
|
+
* before trusting any claim. Pass `null` on logout to go anonymous.
|
|
131
|
+
*/
|
|
132
|
+
identifyWithToken(token: string | null): void;
|
|
133
|
+
/**
|
|
134
|
+
* Publish the current screen's context so the assistant can answer
|
|
135
|
+
* page-aware questions. Pass `null` to clear.
|
|
136
|
+
*/
|
|
137
|
+
publishPageContext(context: MushiPageContext | null): void;
|
|
107
138
|
/** Attach arbitrary key/value metadata to subsequent reports. */
|
|
108
139
|
setMetadata(key: string, value: unknown): void;
|
|
109
140
|
/** Set the current screen context attached to subsequent reports. */
|
|
@@ -112,6 +143,10 @@ interface MushiRNInstance {
|
|
|
112
143
|
route?: string;
|
|
113
144
|
feature?: string;
|
|
114
145
|
}): void;
|
|
146
|
+
/** Append a breadcrumb to the repro timeline ring buffer. */
|
|
147
|
+
addBreadcrumb(crumb: Omit<MushiBreadcrumb, 'timestamp'> & {
|
|
148
|
+
timestamp?: number;
|
|
149
|
+
}): void;
|
|
115
150
|
/** List this device's reports ordered by most recent. Returns [] on failure. */
|
|
116
151
|
listMyReports(): Promise<MushiReporterReport[]>;
|
|
117
152
|
/** List admin + reporter comments on a specific report. */
|
|
@@ -131,9 +166,10 @@ interface MushiRNInstance {
|
|
|
131
166
|
/** Returns the current user's reputation + point totals. */
|
|
132
167
|
getReputation(): Promise<MushiReputationResult | null>;
|
|
133
168
|
}
|
|
134
|
-
declare function MushiProvider({ children,
|
|
169
|
+
declare function MushiProvider({ children, config: configProp, ...barePropConfig }: MushiRNConfig & {
|
|
135
170
|
children: ReactNode;
|
|
136
|
-
|
|
171
|
+
config?: Partial<MushiRNConfig>;
|
|
172
|
+
}): react.JSX.Element;
|
|
137
173
|
|
|
138
174
|
declare function useMushi(): MushiRNInstance | null;
|
|
139
175
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
1
|
import * as react from 'react';
|
|
3
2
|
import { ReactNode, FC, ReactElement } from 'react';
|
|
4
|
-
import { MushiRewardsConfig, MushiReporterReport, MushiReporterComment, MushiHallOfFameEntry, MushiTierResult, MushiReputationResult } from '@mushi-mushi/core';
|
|
3
|
+
import { MushiRewardsConfig, MushiPageContext, MushiBreadcrumb, MushiReporterReport, MushiReporterComment, MushiHallOfFameEntry, MushiTierResult, MushiReputationResult } from '@mushi-mushi/core';
|
|
5
4
|
export { MushiHallOfFameEntry, MushiReporterComment, MushiReporterReport } from '@mushi-mushi/core';
|
|
6
5
|
|
|
7
6
|
interface ConsoleEntry {
|
|
@@ -72,8 +71,19 @@ interface MushiRNConfig {
|
|
|
72
71
|
console?: boolean;
|
|
73
72
|
network?: boolean;
|
|
74
73
|
navigation?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Capture a screenshot of the current screen when the widget opens.
|
|
76
|
+
* Defaults to `true`. Requires the optional `react-native-view-shot`
|
|
77
|
+
* peer dependency — when it isn't installed, capture is skipped silently
|
|
78
|
+
* (the report is still sent without an image). The user always sees a
|
|
79
|
+
* thumbnail in the sheet and can remove it before submitting, so
|
|
80
|
+
* sensitive screens (balances, PII) are never sent without consent.
|
|
81
|
+
*/
|
|
82
|
+
screenshot?: boolean;
|
|
75
83
|
maxConsoleEntries?: number;
|
|
76
84
|
maxNetworkEntries?: number;
|
|
85
|
+
/** Hard cap on the breadcrumb / repro-timeline ring buffer. Default 50. */
|
|
86
|
+
maxBreadcrumbs?: number;
|
|
77
87
|
};
|
|
78
88
|
storage?: {
|
|
79
89
|
maxQueueSize?: number;
|
|
@@ -104,6 +114,27 @@ interface MushiRNInstance {
|
|
|
104
114
|
provider?: string;
|
|
105
115
|
[k: string]: unknown;
|
|
106
116
|
}): void;
|
|
117
|
+
/**
|
|
118
|
+
* Alias for {@link identify} matching the web SDK's `Mushi.setUser()`.
|
|
119
|
+
* Accepts an object so hosts can call `setUser({ id, email, name })`.
|
|
120
|
+
*/
|
|
121
|
+
setUser(user: {
|
|
122
|
+
id: string;
|
|
123
|
+
email?: string;
|
|
124
|
+
name?: string;
|
|
125
|
+
provider?: string;
|
|
126
|
+
}): void;
|
|
127
|
+
/**
|
|
128
|
+
* Identify the current end user with a signed Mushi identity JWT minted by
|
|
129
|
+
* the host server. Mirrors the web SDK; the backend verifies the signature
|
|
130
|
+
* before trusting any claim. Pass `null` on logout to go anonymous.
|
|
131
|
+
*/
|
|
132
|
+
identifyWithToken(token: string | null): void;
|
|
133
|
+
/**
|
|
134
|
+
* Publish the current screen's context so the assistant can answer
|
|
135
|
+
* page-aware questions. Pass `null` to clear.
|
|
136
|
+
*/
|
|
137
|
+
publishPageContext(context: MushiPageContext | null): void;
|
|
107
138
|
/** Attach arbitrary key/value metadata to subsequent reports. */
|
|
108
139
|
setMetadata(key: string, value: unknown): void;
|
|
109
140
|
/** Set the current screen context attached to subsequent reports. */
|
|
@@ -112,6 +143,10 @@ interface MushiRNInstance {
|
|
|
112
143
|
route?: string;
|
|
113
144
|
feature?: string;
|
|
114
145
|
}): void;
|
|
146
|
+
/** Append a breadcrumb to the repro timeline ring buffer. */
|
|
147
|
+
addBreadcrumb(crumb: Omit<MushiBreadcrumb, 'timestamp'> & {
|
|
148
|
+
timestamp?: number;
|
|
149
|
+
}): void;
|
|
115
150
|
/** List this device's reports ordered by most recent. Returns [] on failure. */
|
|
116
151
|
listMyReports(): Promise<MushiReporterReport[]>;
|
|
117
152
|
/** List admin + reporter comments on a specific report. */
|
|
@@ -131,9 +166,10 @@ interface MushiRNInstance {
|
|
|
131
166
|
/** Returns the current user's reputation + point totals. */
|
|
132
167
|
getReputation(): Promise<MushiReputationResult | null>;
|
|
133
168
|
}
|
|
134
|
-
declare function MushiProvider({ children,
|
|
169
|
+
declare function MushiProvider({ children, config: configProp, ...barePropConfig }: MushiRNConfig & {
|
|
135
170
|
children: ReactNode;
|
|
136
|
-
|
|
171
|
+
config?: Partial<MushiRNConfig>;
|
|
172
|
+
}): react.JSX.Element;
|
|
137
173
|
|
|
138
174
|
declare function useMushi(): MushiRNInstance | null;
|
|
139
175
|
|
package/dist/index.js
CHANGED
|
@@ -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 {
|
|
@@ -309,10 +335,17 @@ import {
|
|
|
309
335
|
ScrollView,
|
|
310
336
|
useColorScheme
|
|
311
337
|
} from "react-native";
|
|
338
|
+
import { mushiPalette } from "@mushi-mushi/core";
|
|
312
339
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
313
340
|
var { height: SCREEN_HEIGHT } = Dimensions2.get("window");
|
|
314
341
|
var SHEET_HEIGHT = SCREEN_HEIGHT * 0.55;
|
|
315
342
|
var DISMISS_THRESHOLD = 80;
|
|
343
|
+
var NEON = {
|
|
344
|
+
accent: "#0FFF50",
|
|
345
|
+
accentHover: "#00E646",
|
|
346
|
+
ink: "#0a1a0a",
|
|
347
|
+
border: "#00C43A"
|
|
348
|
+
};
|
|
316
349
|
var CATEGORIES = [
|
|
317
350
|
{ key: "bug", emoji: "\u{1F41B}", label: "Bug" },
|
|
318
351
|
{ key: "slow", emoji: "\u{1F422}", label: "Slow" },
|
|
@@ -459,7 +492,8 @@ var MushiBottomSheet = ({
|
|
|
459
492
|
}
|
|
460
493
|
};
|
|
461
494
|
const activeScreenshot = screenshotDataUrl && screenshotAttached ? screenshotDataUrl : null;
|
|
462
|
-
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 };
|
|
463
497
|
const canSubmit = !!category && description.trim().length > 0 && phase === "form";
|
|
464
498
|
return /* @__PURE__ */ jsx(Modal, { visible, transparent: true, animationType: "none", statusBarTranslucent: true, children: /* @__PURE__ */ jsxs(
|
|
465
499
|
KeyboardAvoidingView,
|
|
@@ -484,6 +518,10 @@ var MushiBottomSheet = ({
|
|
|
484
518
|
],
|
|
485
519
|
children: [
|
|
486
520
|
/* @__PURE__ */ jsx(View, { ...panResponder.panHandlers, style: s.handleArea, children: /* @__PURE__ */ jsx(View, { style: [s.handle, { backgroundColor: colors.sub }] }) }),
|
|
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
|
+
] }),
|
|
487
525
|
/* @__PURE__ */ jsx(View, { style: s.tabRow, children: ["report", "inbox", "community"].map((tab) => /* @__PURE__ */ jsx(
|
|
488
526
|
TouchableOpacity,
|
|
489
527
|
{
|
|
@@ -493,7 +531,7 @@ var MushiBottomSheet = ({
|
|
|
493
531
|
else setSelectedReportId(null);
|
|
494
532
|
},
|
|
495
533
|
style: [s.tabBtn, sheetTab === tab && { borderBottomColor: colors.accent }],
|
|
496
|
-
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" })
|
|
497
535
|
},
|
|
498
536
|
tab
|
|
499
537
|
)) }),
|
|
@@ -506,7 +544,7 @@ var MushiBottomSheet = ({
|
|
|
506
544
|
VERIFIABLE_STATUSES.has(
|
|
507
545
|
inboxReports.find((r) => r.id === selectedReportId)?.status ?? ""
|
|
508
546
|
) && /* @__PURE__ */ jsxs(View, { style: s.verifyRow, children: [
|
|
509
|
-
/* @__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" }) }),
|
|
510
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" }) })
|
|
511
549
|
] }),
|
|
512
550
|
/* @__PURE__ */ jsx(
|
|
@@ -519,12 +557,11 @@ var MushiBottomSheet = ({
|
|
|
519
557
|
onChangeText: setReplyText
|
|
520
558
|
}
|
|
521
559
|
),
|
|
522
|
-
/* @__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" }) })
|
|
523
561
|
] }) : inboxReports.map((r) => /* @__PURE__ */ jsxs(TouchableOpacity, { style: [s.inboxRow, { borderColor: colors.border }], onPress: () => openThread(r.id), children: [
|
|
524
562
|
/* @__PURE__ */ jsx(Text, { style: { color: colors.text, fontWeight: "600" }, numberOfLines: 1, children: (r.summary ?? r.description ?? "Report").slice(0, 60) }),
|
|
525
563
|
/* @__PURE__ */ jsx(Text, { style: { color: colors.sub, fontSize: 11 }, children: r.status })
|
|
526
564
|
] }, r.id)) }) : sheetTab === "community" ? /* @__PURE__ */ jsxs(ScrollView, { style: s.body, keyboardShouldPersistTaps: "handled", children: [
|
|
527
|
-
/* @__PURE__ */ jsx(Text, { style: [s.title, { color: colors.text }], children: "\u{1F310} Mushi Community" }),
|
|
528
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." }),
|
|
529
566
|
/* @__PURE__ */ jsx(
|
|
530
567
|
TextInput,
|
|
@@ -538,7 +575,7 @@ var MushiBottomSheet = ({
|
|
|
538
575
|
textContentType: "emailAddress"
|
|
539
576
|
}
|
|
540
577
|
),
|
|
541
|
-
/* @__PURE__ */ jsx(TouchableOpacity, { style: [s.submitBtn, { backgroundColor: colors.accent }], children: /* @__PURE__ */ jsx(Text, { style: s.submitText, children: "Send sign-in link \u2192" }) }),
|
|
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" }) }),
|
|
542
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." })
|
|
543
580
|
] }) : phase === "sent" ? /* @__PURE__ */ jsxs(View, { style: s.sentWrap, children: [
|
|
544
581
|
/* @__PURE__ */ jsx(Text, { style: [s.sentEmoji], children: "\u2705" }),
|
|
@@ -550,7 +587,7 @@ var MushiBottomSheet = ({
|
|
|
550
587
|
keyboardShouldPersistTaps: "handled",
|
|
551
588
|
contentContainerStyle: { paddingBottom: 24 },
|
|
552
589
|
children: [
|
|
553
|
-
/* @__PURE__ */ jsx(Text, { style: [s.
|
|
590
|
+
/* @__PURE__ */ jsx(Text, { style: [s.stepLabel, { color: colors.sub }], children: "What kind of issue?" }),
|
|
554
591
|
/* @__PURE__ */ jsx(View, { style: s.catRow, children: CATEGORIES.map((c) => {
|
|
555
592
|
const active = category === c.key;
|
|
556
593
|
return /* @__PURE__ */ jsxs(
|
|
@@ -572,7 +609,7 @@ var MushiBottomSheet = ({
|
|
|
572
609
|
{
|
|
573
610
|
style: [
|
|
574
611
|
s.catLabel,
|
|
575
|
-
{ color: active ?
|
|
612
|
+
{ color: active ? colors.accentInk : colors.text }
|
|
576
613
|
],
|
|
577
614
|
children: c.label
|
|
578
615
|
}
|
|
@@ -638,7 +675,7 @@ var MushiBottomSheet = ({
|
|
|
638
675
|
s.submitBtn,
|
|
639
676
|
{ backgroundColor: canSubmit ? colors.accent : colors.disabled }
|
|
640
677
|
],
|
|
641
|
-
children: /* @__PURE__ */ jsx(Text, { style: [s.submitText,
|
|
678
|
+
children: /* @__PURE__ */ jsx(Text, { style: [s.submitText, { color: canSubmit ? colors.accentInk : colors.disabledText }], children: phase === "sending" ? "Sending\u2026" : "Submit report" })
|
|
642
679
|
}
|
|
643
680
|
)
|
|
644
681
|
]
|
|
@@ -654,7 +691,13 @@ var MushiBottomSheet = ({
|
|
|
654
691
|
var s = StyleSheet.create({
|
|
655
692
|
flex: { flex: 1 },
|
|
656
693
|
backdrop: {
|
|
657
|
-
|
|
694
|
+
// Inlined StyleSheet.absoluteFillObject — RN 0.86 dropped it from the
|
|
695
|
+
// StyleSheet TS types; the literal is equivalent and works on all RN >=0.72.
|
|
696
|
+
position: "absolute",
|
|
697
|
+
top: 0,
|
|
698
|
+
left: 0,
|
|
699
|
+
right: 0,
|
|
700
|
+
bottom: 0
|
|
658
701
|
},
|
|
659
702
|
sheet: {
|
|
660
703
|
position: "absolute",
|
|
@@ -681,6 +724,24 @@ var s = StyleSheet.create({
|
|
|
681
724
|
borderRadius: 3,
|
|
682
725
|
opacity: 0.5
|
|
683
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
|
+
},
|
|
684
745
|
body: {
|
|
685
746
|
paddingHorizontal: 20,
|
|
686
747
|
paddingTop: 4
|
|
@@ -690,6 +751,14 @@ var s = StyleSheet.create({
|
|
|
690
751
|
fontWeight: "700",
|
|
691
752
|
marginBottom: 16
|
|
692
753
|
},
|
|
754
|
+
stepLabel: {
|
|
755
|
+
fontSize: 12,
|
|
756
|
+
fontWeight: "600",
|
|
757
|
+
letterSpacing: 0.2,
|
|
758
|
+
marginTop: 8,
|
|
759
|
+
marginBottom: 10,
|
|
760
|
+
textTransform: "uppercase"
|
|
761
|
+
},
|
|
693
762
|
catRow: {
|
|
694
763
|
flexDirection: "row",
|
|
695
764
|
justifyContent: "space-between",
|
|
@@ -825,6 +894,7 @@ import {
|
|
|
825
894
|
Dimensions as Dimensions3,
|
|
826
895
|
useColorScheme as useColorScheme2
|
|
827
896
|
} from "react-native";
|
|
897
|
+
import { mushiPalette as mushiPalette2, MUSHI_COPY } from "@mushi-mushi/core";
|
|
828
898
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
829
899
|
var FAB_SIZE = 56;
|
|
830
900
|
var EDGE_PADDING = 12;
|
|
@@ -876,7 +946,7 @@ var MushiFloatingButton = ({
|
|
|
876
946
|
const currentX = useRef2(baseX);
|
|
877
947
|
const currentY = useRef2(0);
|
|
878
948
|
const isDragging = useRef2(false);
|
|
879
|
-
const accentColor = accent ?? (dark ? "
|
|
949
|
+
const accentColor = accent ?? mushiPalette2(dark ? "dark" : "light").accent;
|
|
880
950
|
useEffect2(() => {
|
|
881
951
|
if (!persist) return;
|
|
882
952
|
void loadStoredPos().then((pos) => {
|
|
@@ -986,7 +1056,7 @@ var MushiFloatingButton = ({
|
|
|
986
1056
|
onStartShouldSetResponder: () => !draggable,
|
|
987
1057
|
onResponderGrant: handlePressIn,
|
|
988
1058
|
onResponderRelease: handlePressOut,
|
|
989
|
-
children: /* @__PURE__ */ jsx2(Animated2.Text, { style: s2.emoji, children:
|
|
1059
|
+
children: /* @__PURE__ */ jsx2(Animated2.Text, { style: s2.emoji, children: MUSHI_COPY.triggerText })
|
|
990
1060
|
}
|
|
991
1061
|
);
|
|
992
1062
|
};
|
|
@@ -1009,14 +1079,53 @@ var s2 = StyleSheet2.create({
|
|
|
1009
1079
|
}
|
|
1010
1080
|
});
|
|
1011
1081
|
|
|
1082
|
+
// src/version.ts
|
|
1083
|
+
var MUSHI_SDK_PACKAGE = "@mushi-mushi/react-native";
|
|
1084
|
+
var MUSHI_SDK_VERSION = "0.18.1" ? "0.18.1" : "0.17.0";
|
|
1085
|
+
|
|
1012
1086
|
// src/provider.tsx
|
|
1013
1087
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1014
1088
|
var MushiContext = createContext(null);
|
|
1015
|
-
function
|
|
1089
|
+
function breadcrumbToTimelineKind(category) {
|
|
1090
|
+
switch (category) {
|
|
1091
|
+
case "navigation":
|
|
1092
|
+
return "route";
|
|
1093
|
+
case "ui.click":
|
|
1094
|
+
case "ui.tap":
|
|
1095
|
+
return "click";
|
|
1096
|
+
case "xhr":
|
|
1097
|
+
case "fetch":
|
|
1098
|
+
case "network":
|
|
1099
|
+
return "request";
|
|
1100
|
+
default:
|
|
1101
|
+
return "log";
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
function MushiProvider({ children, config: configProp, ...barePropConfig }) {
|
|
1105
|
+
const proc = globalThis.process;
|
|
1106
|
+
const envProjectId = proc?.env?.MUSHI_PROJECT_ID || proc?.env?.EXPO_PUBLIC_MUSHI_PROJECT_ID;
|
|
1107
|
+
const envApiKey = proc?.env?.MUSHI_API_KEY || proc?.env?.EXPO_PUBLIC_MUSHI_API_KEY;
|
|
1108
|
+
const config = {
|
|
1109
|
+
...envProjectId ? { projectId: envProjectId } : {},
|
|
1110
|
+
...envApiKey ? { apiKey: envApiKey } : {},
|
|
1111
|
+
...configProp,
|
|
1112
|
+
...barePropConfig
|
|
1113
|
+
};
|
|
1114
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
1115
|
+
if (!config.projectId || !config.apiKey) {
|
|
1116
|
+
console.warn(
|
|
1117
|
+
"[MushiProvider] Missing projectId or apiKey \u2014 set MUSHI_PROJECT_ID / MUSHI_API_KEY (or EXPO_PUBLIC_* equivalents) or pass them as props."
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1016
1121
|
const consoleRef = useRef3(null);
|
|
1017
1122
|
const networkRef = useRef3(null);
|
|
1018
1123
|
const queueRef = useRef3(null);
|
|
1019
1124
|
const apiClientRef = useRef3(null);
|
|
1125
|
+
const sessionIdRef = useRef3(`sess_${newUuid()}`);
|
|
1126
|
+
const breadcrumbsRef = useRef3(
|
|
1127
|
+
createBreadcrumbBuffer({ max: config.capture?.maxBreadcrumbs ?? 50 })
|
|
1128
|
+
);
|
|
1020
1129
|
const [sheetVisible, setSheetVisible] = useState2(false);
|
|
1021
1130
|
const [sheetScreenshot, setSheetScreenshot] = useState2(null);
|
|
1022
1131
|
const reporterTokenRef = useRef3(`rn-${config.projectId}-anon`);
|
|
@@ -1027,6 +1136,8 @@ function MushiProvider({ children, ...config }) {
|
|
|
1027
1136
|
resolveReporterTokenReady = resolve;
|
|
1028
1137
|
})
|
|
1029
1138
|
);
|
|
1139
|
+
const userTokenRef = useRef3(null);
|
|
1140
|
+
const pageContextRef = useRef3(null);
|
|
1030
1141
|
const rawEndpoint = config.apiEndpoint ?? config.endpoint;
|
|
1031
1142
|
if (rawEndpoint !== void 0 && rawEndpoint.trim() === "") {
|
|
1032
1143
|
throw new Error(
|
|
@@ -1047,7 +1158,10 @@ function MushiProvider({ children, ...config }) {
|
|
|
1047
1158
|
apiClientRef.current = createApiClient({
|
|
1048
1159
|
projectId: config.projectId,
|
|
1049
1160
|
apiKey: config.apiKey,
|
|
1050
|
-
apiEndpoint
|
|
1161
|
+
apiEndpoint,
|
|
1162
|
+
getUserToken: () => userTokenRef.current,
|
|
1163
|
+
sdkPackage: MUSHI_SDK_PACKAGE,
|
|
1164
|
+
sdkVersion: MUSHI_SDK_VERSION
|
|
1051
1165
|
});
|
|
1052
1166
|
queueRef.current = new AsyncStorageQueue({
|
|
1053
1167
|
maxSize: config.storage?.maxQueueSize,
|
|
@@ -1060,6 +1174,12 @@ function MushiProvider({ children, ...config }) {
|
|
|
1060
1174
|
});
|
|
1061
1175
|
queueRef.current.flush().catch(() => {
|
|
1062
1176
|
});
|
|
1177
|
+
breadcrumbsRef.current.add({
|
|
1178
|
+
category: "lifecycle",
|
|
1179
|
+
level: "info",
|
|
1180
|
+
message: "Mushi SDK initialised",
|
|
1181
|
+
data: { sdk: MUSHI_SDK_PACKAGE, version: MUSHI_SDK_VERSION }
|
|
1182
|
+
});
|
|
1063
1183
|
(async () => {
|
|
1064
1184
|
const TOKEN_KEY = "@mushi:reporter_token";
|
|
1065
1185
|
try {
|
|
@@ -1101,6 +1221,17 @@ function MushiProvider({ children, ...config }) {
|
|
|
1101
1221
|
return () => unsubscribe?.();
|
|
1102
1222
|
}, []);
|
|
1103
1223
|
const open = useCallback3(() => {
|
|
1224
|
+
breadcrumbsRef.current.add({
|
|
1225
|
+
category: "lifecycle",
|
|
1226
|
+
level: "info",
|
|
1227
|
+
message: "Widget opened",
|
|
1228
|
+
...screenRef.current?.name ? { data: { screen: screenRef.current.name } } : {}
|
|
1229
|
+
});
|
|
1230
|
+
if (config.capture?.screenshot === false) {
|
|
1231
|
+
setSheetScreenshot(null);
|
|
1232
|
+
setSheetVisible(true);
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1104
1235
|
let vshot = null;
|
|
1105
1236
|
try {
|
|
1106
1237
|
vshot = __require("react-native-view-shot");
|
|
@@ -1118,7 +1249,7 @@ function MushiProvider({ children, ...config }) {
|
|
|
1118
1249
|
}).finally(() => {
|
|
1119
1250
|
setSheetVisible(true);
|
|
1120
1251
|
});
|
|
1121
|
-
}, []);
|
|
1252
|
+
}, [config.capture?.screenshot]);
|
|
1122
1253
|
const close = useCallback3(() => setSheetVisible(false), []);
|
|
1123
1254
|
const attachTo = useCallback3(() => ({ onPress: open }), [open]);
|
|
1124
1255
|
useEffect3(() => {
|
|
@@ -1152,14 +1283,35 @@ function MushiProvider({ children, ...config }) {
|
|
|
1152
1283
|
const submitReport = useCallback3(
|
|
1153
1284
|
async (data) => {
|
|
1154
1285
|
const deviceInfo = getDeviceInfo();
|
|
1286
|
+
breadcrumbsRef.current.add({
|
|
1287
|
+
category: "lifecycle",
|
|
1288
|
+
level: "info",
|
|
1289
|
+
message: "Report submitted",
|
|
1290
|
+
data: { category: data.category }
|
|
1291
|
+
});
|
|
1155
1292
|
const contextMeta = {};
|
|
1156
|
-
if (userRef.current?.id)
|
|
1293
|
+
if (userRef.current?.id) {
|
|
1294
|
+
contextMeta.user = {
|
|
1295
|
+
id: userRef.current.id,
|
|
1296
|
+
...userRef.current.email ? { email: userRef.current.email } : {},
|
|
1297
|
+
...userRef.current.name ? { name: userRef.current.name } : {},
|
|
1298
|
+
...userRef.current.provider ? { provider: userRef.current.provider } : {}
|
|
1299
|
+
};
|
|
1300
|
+
contextMeta.userId = userRef.current.id;
|
|
1301
|
+
}
|
|
1157
1302
|
if (userRef.current?.email) contextMeta.userEmail = userRef.current.email;
|
|
1158
1303
|
if (userRef.current?.name) contextMeta.userName = userRef.current.name;
|
|
1159
1304
|
if (screenRef.current?.name) contextMeta.screenName = screenRef.current.name;
|
|
1160
1305
|
if (screenRef.current?.route) contextMeta.screenRoute = screenRef.current.route;
|
|
1161
1306
|
if (screenRef.current?.feature) contextMeta.screenFeature = screenRef.current.feature;
|
|
1162
1307
|
const merged = Object.keys(metadataRef.current).length > 0 ? { ...contextMeta, ...metadataRef.current } : Object.keys(contextMeta).length > 0 ? contextMeta : void 0;
|
|
1308
|
+
const breadcrumbs = breadcrumbsRef.current.getAll();
|
|
1309
|
+
const timeline = breadcrumbs.map((c) => ({
|
|
1310
|
+
ts: c.timestamp,
|
|
1311
|
+
kind: breadcrumbToTimelineKind(c.category),
|
|
1312
|
+
payload: { message: c.message, level: c.level, ...c.data ?? {} }
|
|
1313
|
+
}));
|
|
1314
|
+
const fingerprintHash = getDeviceFingerprintHash(deviceInfo);
|
|
1163
1315
|
const report = {
|
|
1164
1316
|
id: newUuid(),
|
|
1165
1317
|
projectId: config.projectId,
|
|
@@ -1182,7 +1334,14 @@ function MushiProvider({ children, ...config }) {
|
|
|
1182
1334
|
})) ?? [],
|
|
1183
1335
|
screenshotDataUrl: data.screenshotDataUrl,
|
|
1184
1336
|
metadata: merged,
|
|
1337
|
+
sessionId: sessionIdRef.current,
|
|
1185
1338
|
reporterToken: reporterTokenRef.current,
|
|
1339
|
+
...fingerprintHash ? { fingerprintHash } : {},
|
|
1340
|
+
...deviceInfo.appVersion ? { appVersion: deviceInfo.appVersion } : {},
|
|
1341
|
+
sdkPackage: MUSHI_SDK_PACKAGE,
|
|
1342
|
+
sdkVersion: MUSHI_SDK_VERSION,
|
|
1343
|
+
...breadcrumbs.length > 0 ? { breadcrumbs } : {},
|
|
1344
|
+
...timeline.length > 0 ? { timeline } : {},
|
|
1186
1345
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1187
1346
|
};
|
|
1188
1347
|
const client = apiClientRef.current;
|
|
@@ -1240,11 +1399,53 @@ function MushiProvider({ children, ...config }) {
|
|
|
1240
1399
|
provider: traits?.provider
|
|
1241
1400
|
};
|
|
1242
1401
|
},
|
|
1402
|
+
setUser(user) {
|
|
1403
|
+
userRef.current = {
|
|
1404
|
+
id: user.id,
|
|
1405
|
+
email: user.email,
|
|
1406
|
+
name: user.name,
|
|
1407
|
+
provider: user.provider
|
|
1408
|
+
};
|
|
1409
|
+
},
|
|
1410
|
+
identifyWithToken(token) {
|
|
1411
|
+
userTokenRef.current = token && typeof token === "string" ? token : null;
|
|
1412
|
+
if (userTokenRef.current) {
|
|
1413
|
+
const claims = parseIdentityToken(userTokenRef.current);
|
|
1414
|
+
if (claims?.sub) {
|
|
1415
|
+
userRef.current = {
|
|
1416
|
+
id: claims.sub,
|
|
1417
|
+
...claims.email ? { email: claims.email } : {},
|
|
1418
|
+
...claims.name ? { name: claims.name } : {}
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
},
|
|
1423
|
+
publishPageContext(context) {
|
|
1424
|
+
pageContextRef.current = context && context.route ? context : null;
|
|
1425
|
+
},
|
|
1243
1426
|
setMetadata(key, value) {
|
|
1244
1427
|
metadataRef.current[key] = value;
|
|
1245
1428
|
},
|
|
1246
1429
|
setScreen(screen) {
|
|
1430
|
+
const prev = screenRef.current?.name;
|
|
1247
1431
|
screenRef.current = screen;
|
|
1432
|
+
if (screen.name && screen.name !== prev) {
|
|
1433
|
+
breadcrumbsRef.current.add({
|
|
1434
|
+
category: "navigation",
|
|
1435
|
+
level: "info",
|
|
1436
|
+
message: `Navigated to ${screen.name}`,
|
|
1437
|
+
// Always include `route` (fallback to the screen name) so the
|
|
1438
|
+
// admin repro-timeline renders the destination instead of
|
|
1439
|
+
// "unknown route" when the host only set a screen name.
|
|
1440
|
+
data: {
|
|
1441
|
+
route: screen.route ?? screen.name,
|
|
1442
|
+
...screen.feature ? { feature: screen.feature } : {}
|
|
1443
|
+
}
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
},
|
|
1447
|
+
addBreadcrumb(crumb) {
|
|
1448
|
+
breadcrumbsRef.current.add(crumb);
|
|
1248
1449
|
},
|
|
1249
1450
|
// Reporter API — scoped to the device's persistent reporter token
|
|
1250
1451
|
async listMyReports() {
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mushi-mushi/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React Native SDK for Mushi Mushi — shake-to-report, bottom sheet widget, full context capture",
|
|
6
|
-
"main": "./dist/index.
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"peerDependencies": {
|
|
@@ -33,15 +33,15 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@mushi-mushi/core": "^1.
|
|
36
|
+
"@mushi-mushi/core": "^1.17.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@types/react": "^19.2.
|
|
40
|
-
"react": "^19.2.
|
|
41
|
-
"react-native": "^0.
|
|
39
|
+
"@types/react": "^19.2.17",
|
|
40
|
+
"react": "^19.2.7",
|
|
41
|
+
"react-native": "^0.86.0",
|
|
42
42
|
"tsup": "^8.5.1",
|
|
43
43
|
"typescript": "^6.0.3",
|
|
44
|
-
"vitest": "^4.1.
|
|
44
|
+
"vitest": "^4.1.8"
|
|
45
45
|
},
|
|
46
46
|
"author": "Kenji Sakuramoto",
|
|
47
47
|
"repository": {
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"files": [
|
|
61
61
|
"dist",
|
|
62
62
|
"README.md",
|
|
63
|
+
"CHANGELOG.md",
|
|
63
64
|
"LICENSE",
|
|
64
65
|
"CONTRIBUTING.md",
|
|
65
66
|
"CODE_OF_CONDUCT.md",
|