@mushi-mushi/react-native 0.16.0 → 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 +36 -1
- package/dist/index.cjs +212 -21
- package/dist/index.d.cts +39 -2
- package/dist/index.d.ts +39 -2
- package/dist/index.js +209 -16
- package/package.json +4 -3
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode, FC, ReactElement } from 'react';
|
|
4
|
-
import { MushiRewardsConfig, MushiReporterReport, MushiReporterComment, MushiHallOfFameEntry, MushiTierResult, MushiReputationResult } from '@mushi-mushi/core';
|
|
4
|
+
import { MushiRewardsConfig, MushiPageContext, MushiBreadcrumb, MushiReporterReport, MushiReporterComment, MushiHallOfFameEntry, MushiTierResult, MushiReputationResult } from '@mushi-mushi/core';
|
|
5
5
|
export { MushiHallOfFameEntry, MushiReporterComment, MushiReporterReport } from '@mushi-mushi/core';
|
|
6
6
|
|
|
7
7
|
interface ConsoleEntry {
|
|
@@ -72,8 +72,19 @@ interface MushiRNConfig {
|
|
|
72
72
|
console?: boolean;
|
|
73
73
|
network?: boolean;
|
|
74
74
|
navigation?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Capture a screenshot of the current screen when the widget opens.
|
|
77
|
+
* Defaults to `true`. Requires the optional `react-native-view-shot`
|
|
78
|
+
* peer dependency — when it isn't installed, capture is skipped silently
|
|
79
|
+
* (the report is still sent without an image). The user always sees a
|
|
80
|
+
* thumbnail in the sheet and can remove it before submitting, so
|
|
81
|
+
* sensitive screens (balances, PII) are never sent without consent.
|
|
82
|
+
*/
|
|
83
|
+
screenshot?: boolean;
|
|
75
84
|
maxConsoleEntries?: number;
|
|
76
85
|
maxNetworkEntries?: number;
|
|
86
|
+
/** Hard cap on the breadcrumb / repro-timeline ring buffer. Default 50. */
|
|
87
|
+
maxBreadcrumbs?: number;
|
|
77
88
|
};
|
|
78
89
|
storage?: {
|
|
79
90
|
maxQueueSize?: number;
|
|
@@ -104,6 +115,27 @@ interface MushiRNInstance {
|
|
|
104
115
|
provider?: string;
|
|
105
116
|
[k: string]: unknown;
|
|
106
117
|
}): void;
|
|
118
|
+
/**
|
|
119
|
+
* Alias for {@link identify} matching the web SDK's `Mushi.setUser()`.
|
|
120
|
+
* Accepts an object so hosts can call `setUser({ id, email, name })`.
|
|
121
|
+
*/
|
|
122
|
+
setUser(user: {
|
|
123
|
+
id: string;
|
|
124
|
+
email?: string;
|
|
125
|
+
name?: string;
|
|
126
|
+
provider?: string;
|
|
127
|
+
}): void;
|
|
128
|
+
/**
|
|
129
|
+
* Identify the current end user with a signed Mushi identity JWT minted by
|
|
130
|
+
* the host server. Mirrors the web SDK; the backend verifies the signature
|
|
131
|
+
* before trusting any claim. Pass `null` on logout to go anonymous.
|
|
132
|
+
*/
|
|
133
|
+
identifyWithToken(token: string | null): void;
|
|
134
|
+
/**
|
|
135
|
+
* Publish the current screen's context so the assistant can answer
|
|
136
|
+
* page-aware questions. Pass `null` to clear.
|
|
137
|
+
*/
|
|
138
|
+
publishPageContext(context: MushiPageContext | null): void;
|
|
107
139
|
/** Attach arbitrary key/value metadata to subsequent reports. */
|
|
108
140
|
setMetadata(key: string, value: unknown): void;
|
|
109
141
|
/** Set the current screen context attached to subsequent reports. */
|
|
@@ -112,6 +144,10 @@ interface MushiRNInstance {
|
|
|
112
144
|
route?: string;
|
|
113
145
|
feature?: string;
|
|
114
146
|
}): void;
|
|
147
|
+
/** Append a breadcrumb to the repro timeline ring buffer. */
|
|
148
|
+
addBreadcrumb(crumb: Omit<MushiBreadcrumb, 'timestamp'> & {
|
|
149
|
+
timestamp?: number;
|
|
150
|
+
}): void;
|
|
115
151
|
/** List this device's reports ordered by most recent. Returns [] on failure. */
|
|
116
152
|
listMyReports(): Promise<MushiReporterReport[]>;
|
|
117
153
|
/** List admin + reporter comments on a specific report. */
|
|
@@ -131,8 +167,9 @@ interface MushiRNInstance {
|
|
|
131
167
|
/** Returns the current user's reputation + point totals. */
|
|
132
168
|
getReputation(): Promise<MushiReputationResult | null>;
|
|
133
169
|
}
|
|
134
|
-
declare function MushiProvider({ children,
|
|
170
|
+
declare function MushiProvider({ children, config: configProp, ...barePropConfig }: MushiRNConfig & {
|
|
135
171
|
children: ReactNode;
|
|
172
|
+
config?: Partial<MushiRNConfig>;
|
|
136
173
|
}): react_jsx_runtime.JSX.Element;
|
|
137
174
|
|
|
138
175
|
declare function useMushi(): MushiRNInstance | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { ReactNode, FC, ReactElement } from 'react';
|
|
4
|
-
import { MushiRewardsConfig, MushiReporterReport, MushiReporterComment, MushiHallOfFameEntry, MushiTierResult, MushiReputationResult } from '@mushi-mushi/core';
|
|
4
|
+
import { MushiRewardsConfig, MushiPageContext, MushiBreadcrumb, MushiReporterReport, MushiReporterComment, MushiHallOfFameEntry, MushiTierResult, MushiReputationResult } from '@mushi-mushi/core';
|
|
5
5
|
export { MushiHallOfFameEntry, MushiReporterComment, MushiReporterReport } from '@mushi-mushi/core';
|
|
6
6
|
|
|
7
7
|
interface ConsoleEntry {
|
|
@@ -72,8 +72,19 @@ interface MushiRNConfig {
|
|
|
72
72
|
console?: boolean;
|
|
73
73
|
network?: boolean;
|
|
74
74
|
navigation?: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Capture a screenshot of the current screen when the widget opens.
|
|
77
|
+
* Defaults to `true`. Requires the optional `react-native-view-shot`
|
|
78
|
+
* peer dependency — when it isn't installed, capture is skipped silently
|
|
79
|
+
* (the report is still sent without an image). The user always sees a
|
|
80
|
+
* thumbnail in the sheet and can remove it before submitting, so
|
|
81
|
+
* sensitive screens (balances, PII) are never sent without consent.
|
|
82
|
+
*/
|
|
83
|
+
screenshot?: boolean;
|
|
75
84
|
maxConsoleEntries?: number;
|
|
76
85
|
maxNetworkEntries?: number;
|
|
86
|
+
/** Hard cap on the breadcrumb / repro-timeline ring buffer. Default 50. */
|
|
87
|
+
maxBreadcrumbs?: number;
|
|
77
88
|
};
|
|
78
89
|
storage?: {
|
|
79
90
|
maxQueueSize?: number;
|
|
@@ -104,6 +115,27 @@ interface MushiRNInstance {
|
|
|
104
115
|
provider?: string;
|
|
105
116
|
[k: string]: unknown;
|
|
106
117
|
}): void;
|
|
118
|
+
/**
|
|
119
|
+
* Alias for {@link identify} matching the web SDK's `Mushi.setUser()`.
|
|
120
|
+
* Accepts an object so hosts can call `setUser({ id, email, name })`.
|
|
121
|
+
*/
|
|
122
|
+
setUser(user: {
|
|
123
|
+
id: string;
|
|
124
|
+
email?: string;
|
|
125
|
+
name?: string;
|
|
126
|
+
provider?: string;
|
|
127
|
+
}): void;
|
|
128
|
+
/**
|
|
129
|
+
* Identify the current end user with a signed Mushi identity JWT minted by
|
|
130
|
+
* the host server. Mirrors the web SDK; the backend verifies the signature
|
|
131
|
+
* before trusting any claim. Pass `null` on logout to go anonymous.
|
|
132
|
+
*/
|
|
133
|
+
identifyWithToken(token: string | null): void;
|
|
134
|
+
/**
|
|
135
|
+
* Publish the current screen's context so the assistant can answer
|
|
136
|
+
* page-aware questions. Pass `null` to clear.
|
|
137
|
+
*/
|
|
138
|
+
publishPageContext(context: MushiPageContext | null): void;
|
|
107
139
|
/** Attach arbitrary key/value metadata to subsequent reports. */
|
|
108
140
|
setMetadata(key: string, value: unknown): void;
|
|
109
141
|
/** Set the current screen context attached to subsequent reports. */
|
|
@@ -112,6 +144,10 @@ interface MushiRNInstance {
|
|
|
112
144
|
route?: string;
|
|
113
145
|
feature?: string;
|
|
114
146
|
}): void;
|
|
147
|
+
/** Append a breadcrumb to the repro timeline ring buffer. */
|
|
148
|
+
addBreadcrumb(crumb: Omit<MushiBreadcrumb, 'timestamp'> & {
|
|
149
|
+
timestamp?: number;
|
|
150
|
+
}): void;
|
|
115
151
|
/** List this device's reports ordered by most recent. Returns [] on failure. */
|
|
116
152
|
listMyReports(): Promise<MushiReporterReport[]>;
|
|
117
153
|
/** List admin + reporter comments on a specific report. */
|
|
@@ -131,8 +167,9 @@ interface MushiRNInstance {
|
|
|
131
167
|
/** Returns the current user's reputation + point totals. */
|
|
132
168
|
getReputation(): Promise<MushiReputationResult | null>;
|
|
133
169
|
}
|
|
134
|
-
declare function MushiProvider({ children,
|
|
170
|
+
declare function MushiProvider({ children, config: configProp, ...barePropConfig }: MushiRNConfig & {
|
|
135
171
|
children: ReactNode;
|
|
172
|
+
config?: Partial<MushiRNConfig>;
|
|
136
173
|
}): react_jsx_runtime.JSX.Element;
|
|
137
174
|
|
|
138
175
|
declare function useMushi(): MushiRNInstance | null;
|
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
|
]
|
|
@@ -681,6 +718,24 @@ var s = StyleSheet.create({
|
|
|
681
718
|
borderRadius: 3,
|
|
682
719
|
opacity: 0.5
|
|
683
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
|
+
},
|
|
684
739
|
body: {
|
|
685
740
|
paddingHorizontal: 20,
|
|
686
741
|
paddingTop: 4
|
|
@@ -690,6 +745,14 @@ var s = StyleSheet.create({
|
|
|
690
745
|
fontWeight: "700",
|
|
691
746
|
marginBottom: 16
|
|
692
747
|
},
|
|
748
|
+
stepLabel: {
|
|
749
|
+
fontSize: 12,
|
|
750
|
+
fontWeight: "600",
|
|
751
|
+
letterSpacing: 0.2,
|
|
752
|
+
marginTop: 8,
|
|
753
|
+
marginBottom: 10,
|
|
754
|
+
textTransform: "uppercase"
|
|
755
|
+
},
|
|
693
756
|
catRow: {
|
|
694
757
|
flexDirection: "row",
|
|
695
758
|
justifyContent: "space-between",
|
|
@@ -825,6 +888,7 @@ import {
|
|
|
825
888
|
Dimensions as Dimensions3,
|
|
826
889
|
useColorScheme as useColorScheme2
|
|
827
890
|
} from "react-native";
|
|
891
|
+
import { mushiPalette as mushiPalette2, MUSHI_COPY } from "@mushi-mushi/core";
|
|
828
892
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
829
893
|
var FAB_SIZE = 56;
|
|
830
894
|
var EDGE_PADDING = 12;
|
|
@@ -876,7 +940,7 @@ var MushiFloatingButton = ({
|
|
|
876
940
|
const currentX = useRef2(baseX);
|
|
877
941
|
const currentY = useRef2(0);
|
|
878
942
|
const isDragging = useRef2(false);
|
|
879
|
-
const accentColor = accent ?? (dark ? "
|
|
943
|
+
const accentColor = accent ?? mushiPalette2(dark ? "dark" : "light").accent;
|
|
880
944
|
useEffect2(() => {
|
|
881
945
|
if (!persist) return;
|
|
882
946
|
void loadStoredPos().then((pos) => {
|
|
@@ -986,7 +1050,7 @@ var MushiFloatingButton = ({
|
|
|
986
1050
|
onStartShouldSetResponder: () => !draggable,
|
|
987
1051
|
onResponderGrant: handlePressIn,
|
|
988
1052
|
onResponderRelease: handlePressOut,
|
|
989
|
-
children: /* @__PURE__ */ jsx2(Animated2.Text, { style: s2.emoji, children:
|
|
1053
|
+
children: /* @__PURE__ */ jsx2(Animated2.Text, { style: s2.emoji, children: MUSHI_COPY.triggerText })
|
|
990
1054
|
}
|
|
991
1055
|
);
|
|
992
1056
|
};
|
|
@@ -1009,14 +1073,53 @@ var s2 = StyleSheet2.create({
|
|
|
1009
1073
|
}
|
|
1010
1074
|
});
|
|
1011
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
|
+
|
|
1012
1080
|
// src/provider.tsx
|
|
1013
1081
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1014
1082
|
var MushiContext = createContext(null);
|
|
1015
|
-
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
|
+
}
|
|
1016
1115
|
const consoleRef = useRef3(null);
|
|
1017
1116
|
const networkRef = useRef3(null);
|
|
1018
1117
|
const queueRef = useRef3(null);
|
|
1019
1118
|
const apiClientRef = useRef3(null);
|
|
1119
|
+
const sessionIdRef = useRef3(`sess_${newUuid()}`);
|
|
1120
|
+
const breadcrumbsRef = useRef3(
|
|
1121
|
+
createBreadcrumbBuffer({ max: config.capture?.maxBreadcrumbs ?? 50 })
|
|
1122
|
+
);
|
|
1020
1123
|
const [sheetVisible, setSheetVisible] = useState2(false);
|
|
1021
1124
|
const [sheetScreenshot, setSheetScreenshot] = useState2(null);
|
|
1022
1125
|
const reporterTokenRef = useRef3(`rn-${config.projectId}-anon`);
|
|
@@ -1027,6 +1130,8 @@ function MushiProvider({ children, ...config }) {
|
|
|
1027
1130
|
resolveReporterTokenReady = resolve;
|
|
1028
1131
|
})
|
|
1029
1132
|
);
|
|
1133
|
+
const userTokenRef = useRef3(null);
|
|
1134
|
+
const pageContextRef = useRef3(null);
|
|
1030
1135
|
const rawEndpoint = config.apiEndpoint ?? config.endpoint;
|
|
1031
1136
|
if (rawEndpoint !== void 0 && rawEndpoint.trim() === "") {
|
|
1032
1137
|
throw new Error(
|
|
@@ -1047,7 +1152,8 @@ function MushiProvider({ children, ...config }) {
|
|
|
1047
1152
|
apiClientRef.current = createApiClient({
|
|
1048
1153
|
projectId: config.projectId,
|
|
1049
1154
|
apiKey: config.apiKey,
|
|
1050
|
-
apiEndpoint
|
|
1155
|
+
apiEndpoint,
|
|
1156
|
+
getUserToken: () => userTokenRef.current
|
|
1051
1157
|
});
|
|
1052
1158
|
queueRef.current = new AsyncStorageQueue({
|
|
1053
1159
|
maxSize: config.storage?.maxQueueSize,
|
|
@@ -1060,6 +1166,12 @@ function MushiProvider({ children, ...config }) {
|
|
|
1060
1166
|
});
|
|
1061
1167
|
queueRef.current.flush().catch(() => {
|
|
1062
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
|
+
});
|
|
1063
1175
|
(async () => {
|
|
1064
1176
|
const TOKEN_KEY = "@mushi:reporter_token";
|
|
1065
1177
|
try {
|
|
@@ -1101,6 +1213,17 @@ function MushiProvider({ children, ...config }) {
|
|
|
1101
1213
|
return () => unsubscribe?.();
|
|
1102
1214
|
}, []);
|
|
1103
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
|
+
}
|
|
1104
1227
|
let vshot = null;
|
|
1105
1228
|
try {
|
|
1106
1229
|
vshot = __require("react-native-view-shot");
|
|
@@ -1118,7 +1241,7 @@ function MushiProvider({ children, ...config }) {
|
|
|
1118
1241
|
}).finally(() => {
|
|
1119
1242
|
setSheetVisible(true);
|
|
1120
1243
|
});
|
|
1121
|
-
}, []);
|
|
1244
|
+
}, [config.capture?.screenshot]);
|
|
1122
1245
|
const close = useCallback3(() => setSheetVisible(false), []);
|
|
1123
1246
|
const attachTo = useCallback3(() => ({ onPress: open }), [open]);
|
|
1124
1247
|
useEffect3(() => {
|
|
@@ -1152,14 +1275,35 @@ function MushiProvider({ children, ...config }) {
|
|
|
1152
1275
|
const submitReport = useCallback3(
|
|
1153
1276
|
async (data) => {
|
|
1154
1277
|
const deviceInfo = getDeviceInfo();
|
|
1278
|
+
breadcrumbsRef.current.add({
|
|
1279
|
+
category: "lifecycle",
|
|
1280
|
+
level: "info",
|
|
1281
|
+
message: "Report submitted",
|
|
1282
|
+
data: { category: data.category }
|
|
1283
|
+
});
|
|
1155
1284
|
const contextMeta = {};
|
|
1156
|
-
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
|
+
}
|
|
1157
1294
|
if (userRef.current?.email) contextMeta.userEmail = userRef.current.email;
|
|
1158
1295
|
if (userRef.current?.name) contextMeta.userName = userRef.current.name;
|
|
1159
1296
|
if (screenRef.current?.name) contextMeta.screenName = screenRef.current.name;
|
|
1160
1297
|
if (screenRef.current?.route) contextMeta.screenRoute = screenRef.current.route;
|
|
1161
1298
|
if (screenRef.current?.feature) contextMeta.screenFeature = screenRef.current.feature;
|
|
1162
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);
|
|
1163
1307
|
const report = {
|
|
1164
1308
|
id: newUuid(),
|
|
1165
1309
|
projectId: config.projectId,
|
|
@@ -1182,7 +1326,14 @@ function MushiProvider({ children, ...config }) {
|
|
|
1182
1326
|
})) ?? [],
|
|
1183
1327
|
screenshotDataUrl: data.screenshotDataUrl,
|
|
1184
1328
|
metadata: merged,
|
|
1329
|
+
sessionId: sessionIdRef.current,
|
|
1185
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 } : {},
|
|
1186
1337
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1187
1338
|
};
|
|
1188
1339
|
const client = apiClientRef.current;
|
|
@@ -1240,11 +1391,53 @@ function MushiProvider({ children, ...config }) {
|
|
|
1240
1391
|
provider: traits?.provider
|
|
1241
1392
|
};
|
|
1242
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
|
+
},
|
|
1243
1418
|
setMetadata(key, value) {
|
|
1244
1419
|
metadataRef.current[key] = value;
|
|
1245
1420
|
},
|
|
1246
1421
|
setScreen(screen) {
|
|
1422
|
+
const prev = screenRef.current?.name;
|
|
1247
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);
|
|
1248
1441
|
},
|
|
1249
1442
|
// Reporter API — scoped to the device's persistent reporter token
|
|
1250
1443
|
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.17.0",
|
|
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,7 +33,7 @@
|
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@mushi-mushi/core": "^1.
|
|
36
|
+
"@mushi-mushi/core": "^1.16.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/react": "^19.2.14",
|
|
@@ -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",
|