@mushi-mushi/react-native 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -4
- package/dist/index.cjs +454 -40
- package/dist/index.d.cts +71 -4
- package/dist/index.d.ts +71 -4
- package/dist/index.js +482 -38
- package/package.json +76 -73
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @mushi-mushi/react-native
|
|
2
2
|
|
|
3
|
-
React Native SDK for Mushi Mushi
|
|
3
|
+
React Native SDK for Mushi Mushi — built-in bottom-sheet widget, floating bug button, offline queue.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -16,20 +16,39 @@ export default function App() {
|
|
|
16
16
|
}
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
The provider renders a floating 🐛 button and a slide-up bottom sheet by default. Control behavior via `widget.trigger`:
|
|
20
|
+
|
|
21
|
+
| Value | Behavior |
|
|
22
|
+
|-------|----------|
|
|
23
|
+
| `'button'` (default) | Shows floating action button |
|
|
24
|
+
| `'manual'` | No auto-UI — call `open()` programmatically |
|
|
25
|
+
|
|
19
26
|
### Hooks
|
|
20
27
|
|
|
21
28
|
```ts
|
|
22
|
-
import { useMushi, useMushiReport } from '@mushi-mushi/react-native'
|
|
29
|
+
import { useMushi, useMushiReport, useMushiWidget } from '@mushi-mushi/react-native'
|
|
23
30
|
|
|
24
31
|
const { submitReport } = useMushiReport()
|
|
32
|
+
const { open, close } = useMushiWidget()
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Standalone Components
|
|
36
|
+
|
|
37
|
+
Use the widget components directly if you need custom integration:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { MushiBottomSheet, MushiFloatingButton } from '@mushi-mushi/react-native'
|
|
25
41
|
```
|
|
26
42
|
|
|
43
|
+
- `MushiBottomSheet` — Conversational bug report modal (category → description → submit). Supports dark/light theme, drag-to-dismiss.
|
|
44
|
+
- `MushiFloatingButton` — Positioned FAB with spring animation. Configurable via `buttonPosition`.
|
|
45
|
+
|
|
27
46
|
### Captures
|
|
28
47
|
|
|
29
48
|
- Device info (platform, OS, screen dimensions)
|
|
30
49
|
- Console logs (monkey-patched)
|
|
31
|
-
- Network requests (fetch
|
|
32
|
-
- Navigation events (React Navigation
|
|
50
|
+
- Network requests (fetch interceptor)
|
|
51
|
+
- Navigation events (React Navigation, optional)
|
|
33
52
|
- Offline queue via AsyncStorage
|
|
34
53
|
|
|
35
54
|
## Peer Dependencies
|
package/dist/index.cjs
CHANGED
|
@@ -31,6 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
AsyncStorageQueue: () => AsyncStorageQueue,
|
|
34
|
+
MushiBottomSheet: () => MushiBottomSheet,
|
|
35
|
+
MushiFloatingButton: () => MushiFloatingButton,
|
|
34
36
|
MushiProvider: () => MushiProvider,
|
|
35
37
|
getDeviceInfo: () => getDeviceInfo,
|
|
36
38
|
setupConsoleCapture: () => setupConsoleCapture,
|
|
@@ -43,7 +45,8 @@ __export(index_exports, {
|
|
|
43
45
|
module.exports = __toCommonJS(index_exports);
|
|
44
46
|
|
|
45
47
|
// src/provider.tsx
|
|
46
|
-
var
|
|
48
|
+
var import_react3 = require("react");
|
|
49
|
+
var import_core = require("@mushi-mushi/core");
|
|
47
50
|
|
|
48
51
|
// src/capture/console-capture.ts
|
|
49
52
|
function setupConsoleCapture(maxEntries = 100) {
|
|
@@ -218,63 +221,472 @@ var AsyncStorageQueue = class {
|
|
|
218
221
|
}
|
|
219
222
|
};
|
|
220
223
|
|
|
221
|
-
// src/
|
|
224
|
+
// src/components/MushiBottomSheet.tsx
|
|
225
|
+
var import_react = require("react");
|
|
226
|
+
var import_react_native2 = require("react-native");
|
|
222
227
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
223
|
-
var
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
228
|
+
var { height: SCREEN_HEIGHT } = import_react_native2.Dimensions.get("window");
|
|
229
|
+
var SHEET_HEIGHT = SCREEN_HEIGHT * 0.55;
|
|
230
|
+
var DISMISS_THRESHOLD = 80;
|
|
231
|
+
var CATEGORIES = [
|
|
232
|
+
{ key: "bug", emoji: "\u{1F41B}", label: "Bug" },
|
|
233
|
+
{ key: "slow", emoji: "\u{1F422}", label: "Slow" },
|
|
234
|
+
{ key: "visual", emoji: "\u{1F3A8}", label: "Visual" },
|
|
235
|
+
{ key: "confusing", emoji: "\u{1F615}", label: "Confusing" },
|
|
236
|
+
{ key: "other", emoji: "\u{1F4AC}", label: "Other" }
|
|
237
|
+
];
|
|
238
|
+
var MushiBottomSheet = ({ visible, onClose }) => {
|
|
239
|
+
const mushi = useMushiContext();
|
|
240
|
+
const scheme = (0, import_react_native2.useColorScheme)();
|
|
241
|
+
const dark = scheme === "dark";
|
|
242
|
+
const translateY = (0, import_react.useRef)(new import_react_native2.Animated.Value(SHEET_HEIGHT)).current;
|
|
243
|
+
const backdropOpacity = (0, import_react.useRef)(new import_react_native2.Animated.Value(0)).current;
|
|
244
|
+
const [category, setCategory] = (0, import_react.useState)(null);
|
|
245
|
+
const [description, setDescription] = (0, import_react.useState)("");
|
|
246
|
+
const [phase, setPhase] = (0, import_react.useState)("form");
|
|
247
|
+
const resetForm = (0, import_react.useCallback)(() => {
|
|
248
|
+
setCategory(null);
|
|
249
|
+
setDescription("");
|
|
250
|
+
setPhase("form");
|
|
251
|
+
}, []);
|
|
252
|
+
const animateIn = (0, import_react.useCallback)(() => {
|
|
253
|
+
import_react_native2.Animated.parallel([
|
|
254
|
+
import_react_native2.Animated.spring(translateY, {
|
|
255
|
+
toValue: 0,
|
|
256
|
+
useNativeDriver: true,
|
|
257
|
+
damping: 20,
|
|
258
|
+
stiffness: 200
|
|
259
|
+
}),
|
|
260
|
+
import_react_native2.Animated.timing(backdropOpacity, {
|
|
261
|
+
toValue: 1,
|
|
262
|
+
duration: 250,
|
|
263
|
+
useNativeDriver: true
|
|
264
|
+
})
|
|
265
|
+
]).start();
|
|
266
|
+
}, [translateY, backdropOpacity]);
|
|
267
|
+
const animateOut = (0, import_react.useCallback)(
|
|
268
|
+
(cb) => {
|
|
269
|
+
import_react_native2.Animated.parallel([
|
|
270
|
+
import_react_native2.Animated.timing(translateY, {
|
|
271
|
+
toValue: SHEET_HEIGHT,
|
|
272
|
+
duration: 220,
|
|
273
|
+
useNativeDriver: true
|
|
274
|
+
}),
|
|
275
|
+
import_react_native2.Animated.timing(backdropOpacity, {
|
|
276
|
+
toValue: 0,
|
|
277
|
+
duration: 200,
|
|
278
|
+
useNativeDriver: true
|
|
279
|
+
})
|
|
280
|
+
]).start(() => {
|
|
281
|
+
resetForm();
|
|
282
|
+
cb?.();
|
|
283
|
+
});
|
|
284
|
+
},
|
|
285
|
+
[translateY, backdropOpacity, resetForm]
|
|
286
|
+
);
|
|
229
287
|
(0, import_react.useEffect)(() => {
|
|
288
|
+
if (visible) animateIn();
|
|
289
|
+
}, [visible, animateIn]);
|
|
290
|
+
const handleClose = (0, import_react.useCallback)(() => {
|
|
291
|
+
animateOut(onClose);
|
|
292
|
+
}, [animateOut, onClose]);
|
|
293
|
+
const panResponder = (0, import_react.useRef)(
|
|
294
|
+
import_react_native2.PanResponder.create({
|
|
295
|
+
onStartShouldSetPanResponder: () => true,
|
|
296
|
+
onMoveShouldSetPanResponder: (_, g) => g.dy > 4,
|
|
297
|
+
onPanResponderMove: (_, g) => {
|
|
298
|
+
if (g.dy > 0) translateY.setValue(g.dy);
|
|
299
|
+
},
|
|
300
|
+
onPanResponderRelease: (_, g) => {
|
|
301
|
+
if (g.dy > DISMISS_THRESHOLD) {
|
|
302
|
+
handleClose();
|
|
303
|
+
} else {
|
|
304
|
+
import_react_native2.Animated.spring(translateY, {
|
|
305
|
+
toValue: 0,
|
|
306
|
+
useNativeDriver: true,
|
|
307
|
+
damping: 20,
|
|
308
|
+
stiffness: 200
|
|
309
|
+
}).start();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
).current;
|
|
314
|
+
const handleSubmit = async () => {
|
|
315
|
+
if (!category || !description.trim() || !mushi) return;
|
|
316
|
+
setPhase("sending");
|
|
317
|
+
try {
|
|
318
|
+
await mushi.submitReport({ category, description: description.trim() });
|
|
319
|
+
setPhase("sent");
|
|
320
|
+
setTimeout(handleClose, 1400);
|
|
321
|
+
} catch {
|
|
322
|
+
setPhase("form");
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
const colors = dark ? { bg: "#1c1c1e", text: "#f2f2f7", sub: "#8e8e93", card: "#2c2c2e", accent: "#0a84ff", border: "#38383a", backdrop: "rgba(0,0,0,0.6)" } : { bg: "#ffffff", text: "#1c1c1e", sub: "#8e8e93", card: "#f2f2f7", accent: "#007aff", border: "#e5e5ea", backdrop: "rgba(0,0,0,0.35)" };
|
|
326
|
+
const canSubmit = !!category && description.trim().length > 0 && phase === "form";
|
|
327
|
+
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)(
|
|
328
|
+
import_react_native2.KeyboardAvoidingView,
|
|
329
|
+
{
|
|
330
|
+
style: s.flex,
|
|
331
|
+
behavior: import_react_native2.Platform.OS === "ios" ? "padding" : void 0,
|
|
332
|
+
children: [
|
|
333
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
334
|
+
import_react_native2.Animated.View,
|
|
335
|
+
{
|
|
336
|
+
style: [s.backdrop, { backgroundColor: colors.backdrop, opacity: backdropOpacity }],
|
|
337
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.TouchableOpacity, { style: s.flex, activeOpacity: 1, onPress: handleClose })
|
|
338
|
+
}
|
|
339
|
+
),
|
|
340
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
341
|
+
import_react_native2.Animated.View,
|
|
342
|
+
{
|
|
343
|
+
style: [
|
|
344
|
+
s.sheet,
|
|
345
|
+
{ backgroundColor: colors.bg, transform: [{ translateY }] }
|
|
346
|
+
],
|
|
347
|
+
children: [
|
|
348
|
+
/* @__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 }] }) }),
|
|
349
|
+
phase === "sent" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.sentWrap, children: [
|
|
350
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentEmoji], children: "\u2705" }),
|
|
351
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.sentText, { color: colors.text }], children: "Report sent!" })
|
|
352
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: s.body, children: [
|
|
353
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [s.title, { color: colors.text }], children: "Report an issue" }),
|
|
354
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: s.catRow, children: CATEGORIES.map((c) => {
|
|
355
|
+
const active = category === c.key;
|
|
356
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
357
|
+
import_react_native2.TouchableOpacity,
|
|
358
|
+
{
|
|
359
|
+
onPress: () => setCategory(c.key),
|
|
360
|
+
activeOpacity: 0.7,
|
|
361
|
+
style: [
|
|
362
|
+
s.catBtn,
|
|
363
|
+
{
|
|
364
|
+
backgroundColor: active ? colors.accent : colors.card,
|
|
365
|
+
borderColor: active ? colors.accent : colors.border
|
|
366
|
+
}
|
|
367
|
+
],
|
|
368
|
+
children: [
|
|
369
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.catEmoji, children: c.emoji }),
|
|
370
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
371
|
+
import_react_native2.Text,
|
|
372
|
+
{
|
|
373
|
+
style: [
|
|
374
|
+
s.catLabel,
|
|
375
|
+
{ color: active ? "#fff" : colors.text }
|
|
376
|
+
],
|
|
377
|
+
children: c.label
|
|
378
|
+
}
|
|
379
|
+
)
|
|
380
|
+
]
|
|
381
|
+
},
|
|
382
|
+
c.key
|
|
383
|
+
);
|
|
384
|
+
}) }),
|
|
385
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
386
|
+
import_react_native2.TextInput,
|
|
387
|
+
{
|
|
388
|
+
style: [
|
|
389
|
+
s.input,
|
|
390
|
+
{
|
|
391
|
+
backgroundColor: colors.card,
|
|
392
|
+
color: colors.text,
|
|
393
|
+
borderColor: colors.border
|
|
394
|
+
}
|
|
395
|
+
],
|
|
396
|
+
placeholder: "What happened?",
|
|
397
|
+
placeholderTextColor: colors.sub,
|
|
398
|
+
multiline: true,
|
|
399
|
+
textAlignVertical: "top",
|
|
400
|
+
value: description,
|
|
401
|
+
onChangeText: setDescription,
|
|
402
|
+
editable: phase === "form"
|
|
403
|
+
}
|
|
404
|
+
),
|
|
405
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
406
|
+
import_react_native2.TouchableOpacity,
|
|
407
|
+
{
|
|
408
|
+
onPress: handleSubmit,
|
|
409
|
+
disabled: !canSubmit,
|
|
410
|
+
activeOpacity: 0.8,
|
|
411
|
+
style: [
|
|
412
|
+
s.submitBtn,
|
|
413
|
+
{ backgroundColor: canSubmit ? colors.accent : colors.border }
|
|
414
|
+
],
|
|
415
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: s.submitText, children: phase === "sending" ? "Sending\u2026" : "Submit" })
|
|
416
|
+
}
|
|
417
|
+
)
|
|
418
|
+
] })
|
|
419
|
+
]
|
|
420
|
+
}
|
|
421
|
+
)
|
|
422
|
+
]
|
|
423
|
+
}
|
|
424
|
+
) });
|
|
425
|
+
};
|
|
426
|
+
var s = import_react_native2.StyleSheet.create({
|
|
427
|
+
flex: { flex: 1 },
|
|
428
|
+
backdrop: {
|
|
429
|
+
...import_react_native2.StyleSheet.absoluteFillObject
|
|
430
|
+
},
|
|
431
|
+
sheet: {
|
|
432
|
+
position: "absolute",
|
|
433
|
+
bottom: 0,
|
|
434
|
+
left: 0,
|
|
435
|
+
right: 0,
|
|
436
|
+
minHeight: SHEET_HEIGHT,
|
|
437
|
+
borderTopLeftRadius: 20,
|
|
438
|
+
borderTopRightRadius: 20,
|
|
439
|
+
paddingBottom: import_react_native2.Platform.OS === "ios" ? 34 : 16,
|
|
440
|
+
shadowColor: "#000",
|
|
441
|
+
shadowOffset: { width: 0, height: -3 },
|
|
442
|
+
shadowOpacity: 0.12,
|
|
443
|
+
shadowRadius: 8,
|
|
444
|
+
elevation: 24
|
|
445
|
+
},
|
|
446
|
+
handleArea: {
|
|
447
|
+
alignItems: "center",
|
|
448
|
+
paddingVertical: 10
|
|
449
|
+
},
|
|
450
|
+
handle: {
|
|
451
|
+
width: 40,
|
|
452
|
+
height: 5,
|
|
453
|
+
borderRadius: 3,
|
|
454
|
+
opacity: 0.5
|
|
455
|
+
},
|
|
456
|
+
body: {
|
|
457
|
+
paddingHorizontal: 20,
|
|
458
|
+
paddingTop: 4
|
|
459
|
+
},
|
|
460
|
+
title: {
|
|
461
|
+
fontSize: 20,
|
|
462
|
+
fontWeight: "700",
|
|
463
|
+
marginBottom: 16
|
|
464
|
+
},
|
|
465
|
+
catRow: {
|
|
466
|
+
flexDirection: "row",
|
|
467
|
+
justifyContent: "space-between",
|
|
468
|
+
marginBottom: 16
|
|
469
|
+
},
|
|
470
|
+
catBtn: {
|
|
471
|
+
alignItems: "center",
|
|
472
|
+
paddingVertical: 10,
|
|
473
|
+
paddingHorizontal: 6,
|
|
474
|
+
borderRadius: 12,
|
|
475
|
+
borderWidth: 1,
|
|
476
|
+
flex: 1,
|
|
477
|
+
marginHorizontal: 3
|
|
478
|
+
},
|
|
479
|
+
catEmoji: {
|
|
480
|
+
fontSize: 22,
|
|
481
|
+
marginBottom: 4
|
|
482
|
+
},
|
|
483
|
+
catLabel: {
|
|
484
|
+
fontSize: 11,
|
|
485
|
+
fontWeight: "600"
|
|
486
|
+
},
|
|
487
|
+
input: {
|
|
488
|
+
borderWidth: 1,
|
|
489
|
+
borderRadius: 12,
|
|
490
|
+
padding: 14,
|
|
491
|
+
fontSize: 15,
|
|
492
|
+
minHeight: 100,
|
|
493
|
+
marginBottom: 16
|
|
494
|
+
},
|
|
495
|
+
submitBtn: {
|
|
496
|
+
borderRadius: 12,
|
|
497
|
+
paddingVertical: 14,
|
|
498
|
+
alignItems: "center"
|
|
499
|
+
},
|
|
500
|
+
submitText: {
|
|
501
|
+
color: "#fff",
|
|
502
|
+
fontSize: 16,
|
|
503
|
+
fontWeight: "700"
|
|
504
|
+
},
|
|
505
|
+
sentWrap: {
|
|
506
|
+
alignItems: "center",
|
|
507
|
+
justifyContent: "center",
|
|
508
|
+
paddingVertical: 60
|
|
509
|
+
},
|
|
510
|
+
sentEmoji: {
|
|
511
|
+
fontSize: 48,
|
|
512
|
+
marginBottom: 12
|
|
513
|
+
},
|
|
514
|
+
sentText: {
|
|
515
|
+
fontSize: 20,
|
|
516
|
+
fontWeight: "600"
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// src/components/MushiFloatingButton.tsx
|
|
521
|
+
var import_react2 = require("react");
|
|
522
|
+
var import_react_native3 = require("react-native");
|
|
523
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
524
|
+
var MushiFloatingButton = ({
|
|
525
|
+
onPress,
|
|
526
|
+
position = "bottom-right"
|
|
527
|
+
}) => {
|
|
528
|
+
const scheme = (0, import_react_native3.useColorScheme)();
|
|
529
|
+
const dark = scheme === "dark";
|
|
530
|
+
const scale = (0, import_react2.useRef)(new import_react_native3.Animated.Value(1)).current;
|
|
531
|
+
const handlePressIn = (0, import_react2.useCallback)(() => {
|
|
532
|
+
import_react_native3.Animated.spring(scale, {
|
|
533
|
+
toValue: 0.85,
|
|
534
|
+
useNativeDriver: true,
|
|
535
|
+
damping: 15,
|
|
536
|
+
stiffness: 300
|
|
537
|
+
}).start();
|
|
538
|
+
}, [scale]);
|
|
539
|
+
const handlePressOut = (0, import_react2.useCallback)(() => {
|
|
540
|
+
import_react_native3.Animated.spring(scale, {
|
|
541
|
+
toValue: 1,
|
|
542
|
+
useNativeDriver: true,
|
|
543
|
+
damping: 10,
|
|
544
|
+
stiffness: 250
|
|
545
|
+
}).start();
|
|
546
|
+
}, [scale]);
|
|
547
|
+
const positionStyle = position === "bottom-left" ? { left: 20 } : { right: 20 };
|
|
548
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
549
|
+
import_react_native3.TouchableWithoutFeedback,
|
|
550
|
+
{
|
|
551
|
+
onPress,
|
|
552
|
+
onPressIn: handlePressIn,
|
|
553
|
+
onPressOut: handlePressOut,
|
|
554
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
555
|
+
import_react_native3.Animated.View,
|
|
556
|
+
{
|
|
557
|
+
style: [
|
|
558
|
+
s2.fab,
|
|
559
|
+
positionStyle,
|
|
560
|
+
{
|
|
561
|
+
backgroundColor: dark ? "#0a84ff" : "#007aff",
|
|
562
|
+
transform: [{ scale }],
|
|
563
|
+
shadowColor: dark ? "#0a84ff" : "#000"
|
|
564
|
+
}
|
|
565
|
+
],
|
|
566
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native3.Animated.Text, { style: s2.emoji, children: "\u{1F41B}" })
|
|
567
|
+
}
|
|
568
|
+
)
|
|
569
|
+
}
|
|
570
|
+
);
|
|
571
|
+
};
|
|
572
|
+
var s2 = import_react_native3.StyleSheet.create({
|
|
573
|
+
fab: {
|
|
574
|
+
position: "absolute",
|
|
575
|
+
bottom: import_react_native3.Platform.OS === "ios" ? 50 : 28,
|
|
576
|
+
width: 56,
|
|
577
|
+
height: 56,
|
|
578
|
+
borderRadius: 28,
|
|
579
|
+
alignItems: "center",
|
|
580
|
+
justifyContent: "center",
|
|
581
|
+
zIndex: 9999,
|
|
582
|
+
elevation: 8,
|
|
583
|
+
shadowOffset: { width: 0, height: 4 },
|
|
584
|
+
shadowOpacity: 0.3,
|
|
585
|
+
shadowRadius: 6
|
|
586
|
+
},
|
|
587
|
+
emoji: {
|
|
588
|
+
fontSize: 26
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
// src/provider.tsx
|
|
593
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
594
|
+
var MushiContext = (0, import_react3.createContext)(null);
|
|
595
|
+
function MushiProvider({ children, ...config }) {
|
|
596
|
+
const consoleRef = (0, import_react3.useRef)(null);
|
|
597
|
+
const networkRef = (0, import_react3.useRef)(null);
|
|
598
|
+
const queueRef = (0, import_react3.useRef)(null);
|
|
599
|
+
const apiClientRef = (0, import_react3.useRef)(null);
|
|
600
|
+
const [sheetVisible, setSheetVisible] = (0, import_react3.useState)(false);
|
|
601
|
+
const apiEndpoint = config.endpoint ?? import_core.DEFAULT_API_ENDPOINT;
|
|
602
|
+
(0, import_react3.useEffect)(() => {
|
|
230
603
|
if (config.capture?.console !== false) {
|
|
231
604
|
consoleRef.current = setupConsoleCapture(config.capture?.maxConsoleEntries);
|
|
232
605
|
}
|
|
233
606
|
if (config.capture?.network !== false) {
|
|
234
607
|
networkRef.current = setupNetworkCapture(config.capture?.maxNetworkEntries, apiEndpoint);
|
|
235
608
|
}
|
|
609
|
+
apiClientRef.current = (0, import_core.createApiClient)({
|
|
610
|
+
projectId: config.projectId,
|
|
611
|
+
apiKey: config.apiKey,
|
|
612
|
+
apiEndpoint
|
|
613
|
+
});
|
|
236
614
|
queueRef.current = new AsyncStorageQueue({
|
|
237
615
|
maxSize: config.storage?.maxQueueSize,
|
|
238
616
|
apiEndpoint,
|
|
239
617
|
apiKey: config.apiKey
|
|
240
618
|
});
|
|
619
|
+
queueRef.current.flush().catch(() => {
|
|
620
|
+
});
|
|
241
621
|
return () => {
|
|
242
622
|
consoleRef.current?.restore();
|
|
243
623
|
networkRef.current?.restore();
|
|
244
624
|
};
|
|
245
625
|
}, []);
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
submitReport: async (data) => {
|
|
626
|
+
const open = (0, import_react3.useCallback)(() => setSheetVisible(true), []);
|
|
627
|
+
const close = (0, import_react3.useCallback)(() => setSheetVisible(false), []);
|
|
628
|
+
const submitReport = (0, import_react3.useCallback)(
|
|
629
|
+
async (data) => {
|
|
630
|
+
const deviceInfo = getDeviceInfo();
|
|
252
631
|
const report = {
|
|
632
|
+
id: `rn-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
253
633
|
projectId: config.projectId,
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
634
|
+
category: data.category,
|
|
635
|
+
description: data.description,
|
|
636
|
+
environment: {
|
|
637
|
+
userAgent: deviceInfo.systemName ?? "ReactNative",
|
|
638
|
+
platform: deviceInfo.platform ?? "mobile",
|
|
639
|
+
language: deviceInfo.locale ?? "en",
|
|
640
|
+
viewport: { width: deviceInfo.screenWidth ?? 0, height: deviceInfo.screenHeight ?? 0 },
|
|
641
|
+
url: "",
|
|
642
|
+
referrer: "",
|
|
643
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
644
|
+
timezone: deviceInfo.timezone ?? "UTC"
|
|
645
|
+
},
|
|
646
|
+
consoleLogs: consoleRef.current?.getEntries() ?? [],
|
|
647
|
+
networkLogs: networkRef.current?.getEntries().map((entry) => ({
|
|
648
|
+
...entry,
|
|
649
|
+
status: entry.status ?? 0
|
|
650
|
+
})) ?? [],
|
|
651
|
+
reporterToken: `rn-${config.projectId}-anon`,
|
|
652
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
258
653
|
};
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
body: JSON.stringify(report)
|
|
264
|
-
});
|
|
265
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
266
|
-
} catch {
|
|
654
|
+
const client = apiClientRef.current;
|
|
655
|
+
if (!client) return;
|
|
656
|
+
const result = await client.submitReport(report);
|
|
657
|
+
if (!result.ok) {
|
|
267
658
|
await queueRef.current?.enqueue(report);
|
|
268
659
|
}
|
|
269
660
|
},
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
661
|
+
[config.projectId, config.apiKey, apiEndpoint]
|
|
662
|
+
);
|
|
663
|
+
const instance = (0, import_react3.useMemo)(
|
|
664
|
+
() => ({
|
|
665
|
+
open,
|
|
666
|
+
close,
|
|
667
|
+
submitReport,
|
|
668
|
+
getDeviceInfo,
|
|
669
|
+
getConsoleEntries: () => consoleRef.current?.getEntries() ?? [],
|
|
670
|
+
getNetworkEntries: () => networkRef.current?.getEntries() ?? []
|
|
671
|
+
}),
|
|
672
|
+
[open, close, submitReport]
|
|
673
|
+
);
|
|
674
|
+
const trigger = config.widget?.trigger ?? "button";
|
|
675
|
+
const showFab = trigger === "button" || trigger === "both";
|
|
676
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MushiContext.Provider, { value: instance, children: [
|
|
677
|
+
children,
|
|
678
|
+
showFab && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
679
|
+
MushiFloatingButton,
|
|
680
|
+
{
|
|
681
|
+
onPress: open,
|
|
682
|
+
position: config.widget?.buttonPosition
|
|
683
|
+
}
|
|
684
|
+
),
|
|
685
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MushiBottomSheet, { visible: sheetVisible, onClose: close })
|
|
686
|
+
] });
|
|
275
687
|
}
|
|
276
688
|
function useMushiContext() {
|
|
277
|
-
return (0,
|
|
689
|
+
return (0, import_react3.useContext)(MushiContext);
|
|
278
690
|
}
|
|
279
691
|
|
|
280
692
|
// src/hooks/useMushi.ts
|
|
@@ -283,10 +695,10 @@ function useMushi() {
|
|
|
283
695
|
}
|
|
284
696
|
|
|
285
697
|
// src/hooks/useMushiReport.ts
|
|
286
|
-
var
|
|
698
|
+
var import_react4 = require("react");
|
|
287
699
|
function useMushiReport() {
|
|
288
700
|
const mushi = useMushiContext();
|
|
289
|
-
const submitReport = (0,
|
|
701
|
+
const submitReport = (0, import_react4.useCallback)(
|
|
290
702
|
async (data) => {
|
|
291
703
|
if (!mushi) throw new Error("MushiProvider not found");
|
|
292
704
|
await mushi.submitReport(data);
|
|
@@ -297,20 +709,20 @@ function useMushiReport() {
|
|
|
297
709
|
}
|
|
298
710
|
|
|
299
711
|
// src/hooks/useMushiWidget.ts
|
|
300
|
-
var
|
|
712
|
+
var import_react5 = require("react");
|
|
301
713
|
function useMushiWidget() {
|
|
302
714
|
const mushi = useMushiContext();
|
|
303
715
|
return {
|
|
304
|
-
open: (0,
|
|
305
|
-
close: (0,
|
|
716
|
+
open: (0, import_react5.useCallback)(() => mushi?.open(), [mushi]),
|
|
717
|
+
close: (0, import_react5.useCallback)(() => mushi?.close(), [mushi])
|
|
306
718
|
};
|
|
307
719
|
}
|
|
308
720
|
|
|
309
721
|
// src/capture/navigation-capture.ts
|
|
310
|
-
var
|
|
722
|
+
var import_react6 = require("react");
|
|
311
723
|
function useNavigationCapture(navigationRef) {
|
|
312
|
-
const historyRef = (0,
|
|
313
|
-
(0,
|
|
724
|
+
const historyRef = (0, import_react6.useRef)([]);
|
|
725
|
+
(0, import_react6.useEffect)(() => {
|
|
314
726
|
if (!navigationRef?.addListener || !navigationRef?.getCurrentRoute) return;
|
|
315
727
|
const unsubscribe = navigationRef.addListener("state", () => {
|
|
316
728
|
const route = navigationRef.getCurrentRoute?.();
|
|
@@ -326,6 +738,8 @@ function useNavigationCapture(navigationRef) {
|
|
|
326
738
|
// Annotate the CommonJS export names for ESM import in node:
|
|
327
739
|
0 && (module.exports = {
|
|
328
740
|
AsyncStorageQueue,
|
|
741
|
+
MushiBottomSheet,
|
|
742
|
+
MushiFloatingButton,
|
|
329
743
|
MushiProvider,
|
|
330
744
|
getDeviceInfo,
|
|
331
745
|
setupConsoleCapture,
|