@mushi-mushi/react-native 0.3.1 → 0.7.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/CODE_OF_CONDUCT.md +1 -1
- package/CONTRIBUTING.md +4 -0
- package/README.md +28 -1
- package/SECURITY.md +75 -1
- package/dist/index.cjs +36 -6
- package/dist/index.d.cts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +36 -6
- package/package.json +6 -2
package/CODE_OF_CONDUCT.md
CHANGED
|
@@ -40,7 +40,7 @@ Examples of unacceptable behavior:
|
|
|
40
40
|
## Enforcement
|
|
41
41
|
|
|
42
42
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
43
|
-
reported to the project team at **
|
|
43
|
+
reported to the project team at **kensaurus@gmail.com**.
|
|
44
44
|
|
|
45
45
|
All complaints will be reviewed and investigated promptly and fairly.
|
|
46
46
|
|
package/CONTRIBUTING.md
CHANGED
|
@@ -33,6 +33,10 @@ pnpm lint # ESLint
|
|
|
33
33
|
pnpm format # Prettier
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
Ad-hoc screenshots captured during UI reviews can live temporarily at the repo
|
|
37
|
+
root, but root-level `*.png` files are intentionally ignored. Canonical
|
|
38
|
+
screenshots that should be versioned belong under `docs/screenshots/`.
|
|
39
|
+
|
|
36
40
|
### Working on a single package
|
|
37
41
|
|
|
38
42
|
```bash
|
package/README.md
CHANGED
|
@@ -25,7 +25,30 @@ The provider renders a floating 🐛 button and a slide-up bottom sheet by defau
|
|
|
25
25
|
| Value | Behavior |
|
|
26
26
|
|-------|----------|
|
|
27
27
|
| `'button'` (default) | Shows floating action button |
|
|
28
|
-
| `'
|
|
28
|
+
| `'shake'` | Installs a shake-to-report listener (no visible UI) |
|
|
29
|
+
| `'both'` | Floating button + shake listener |
|
|
30
|
+
| `'manual'` / `'hidden'` / `'none'` | No auto-UI — call `open()` programmatically |
|
|
31
|
+
| `'attach'` | Hides the default UI; the host renders its own button and calls `attachTo()` |
|
|
32
|
+
|
|
33
|
+
Shake detection requires the optional `expo-sensors` peer dependency. Install
|
|
34
|
+
it once with `expo install expo-sensors` (or `npm install expo-sensors`); the
|
|
35
|
+
listener is wired lazily so apps that ship `'button'` only never pay the cost.
|
|
36
|
+
|
|
37
|
+
`widget.inset` (and the `MushiFloatingButton` `inset` prop) accepts
|
|
38
|
+
`{ top?, right?, bottom?, left? }` in points so the trigger can clear tab
|
|
39
|
+
bars and host CTAs.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
<MushiProvider
|
|
43
|
+
projectId="proj_xxx"
|
|
44
|
+
apiKey="mushi_xxx"
|
|
45
|
+
config={{
|
|
46
|
+
widget: { trigger: 'both', inset: { bottom: 96, right: 16 } },
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
<Navigation />
|
|
50
|
+
</MushiProvider>
|
|
51
|
+
```
|
|
29
52
|
|
|
30
53
|
### Hooks
|
|
31
54
|
|
|
@@ -36,6 +59,10 @@ const { submitReport } = useMushiReport()
|
|
|
36
59
|
const { open, close } = useMushiWidget()
|
|
37
60
|
```
|
|
38
61
|
|
|
62
|
+
The provider's `useMushi()` instance also exposes the cross-platform trigger
|
|
63
|
+
controls — `show()`, `hide()`, `attachTo(elementRefOrId)`, and
|
|
64
|
+
`setTrigger(mode)` — so a host app can toggle the launcher per screen.
|
|
65
|
+
|
|
39
66
|
### Standalone Components
|
|
40
67
|
|
|
41
68
|
Use the widget components directly if you need custom integration:
|
package/SECURITY.md
CHANGED
|
@@ -19,7 +19,7 @@ If you discover a security vulnerability, please report it responsibly.
|
|
|
19
19
|
|
|
20
20
|
**Do NOT open a public GitHub issue.**
|
|
21
21
|
|
|
22
|
-
Instead, email: **
|
|
22
|
+
Instead, email: **kensaurus@gmail.com**
|
|
23
23
|
|
|
24
24
|
Include:
|
|
25
25
|
- Description of the vulnerability
|
|
@@ -48,3 +48,77 @@ We will acknowledge receipt within 48 hours and aim to release a patch within 7
|
|
|
48
48
|
- **Rotate API keys** regularly via the admin console
|
|
49
49
|
- **Enable SSO** for team projects (Enterprise tier)
|
|
50
50
|
- **Review audit logs** periodically for suspicious activity
|
|
51
|
+
|
|
52
|
+
## Supply-chain hardening (how this package is protected)
|
|
53
|
+
|
|
54
|
+
Mushi Mushi is built and published with the controls below. Consumers can
|
|
55
|
+
verify each control independently — the goal is to make tampering both
|
|
56
|
+
difficult and detectable.
|
|
57
|
+
|
|
58
|
+
### Publish-time controls
|
|
59
|
+
|
|
60
|
+
| Control | What it does | How to verify |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| **npm Trusted Publisher (OIDC)** | Every release is published from `.github/workflows/release.yml` on `master` using a short-lived OIDC token. Long-lived `NPM_TOKEN` is not used for publishing. | `npm view @mushi-mushi/<pkg> --json` shows `"trustedPublisher"` populated for recent versions. |
|
|
63
|
+
| **npm provenance attestations** | Every published tarball ships a [Sigstore provenance attestation](https://docs.npmjs.com/generating-provenance-statements) cryptographically linking the tarball to the exact GitHub Actions run that built it. | `npm audit signatures` (run inside any project that depends on `@mushi-mushi/*`) reports `verified registry signatures` and `verified attestations`. The npm web UI shows a "Built and signed on GitHub Actions" badge on each version. |
|
|
64
|
+
| **Pre-publish workspace-protocol guard** | Aborts the publish if `workspace:*` ranges leaked into the tarball (the bug class behind the v0.1.0 incident). | `scripts/check-workspace-protocol.mjs` runs before `changeset publish` in `pnpm release`. |
|
|
65
|
+
| **Post-publish tarball verification** | Re-downloads each just-published tarball and asserts it doesn't contain `workspace:*`. | See the "Verify published tarballs do not contain workspace:*" step in `release.yml`. |
|
|
66
|
+
| **Post-publish `npm audit signatures`** | Re-installs each published version and validates registry signatures + provenance against npm's transparency log. | See the "Audit signatures of installed dependencies" step in `release.yml`. |
|
|
67
|
+
|
|
68
|
+
### Build-time controls
|
|
69
|
+
|
|
70
|
+
| Control | What it does |
|
|
71
|
+
|---|---|
|
|
72
|
+
| **All third-party GitHub Actions pinned to commit SHAs** | Every `uses:` in every workflow under `.github/workflows/` is pinned to a 40-character commit SHA with a version comment. Floating tags (`@v4`, `@main`) are mutable and were the entry point for the [tj-actions/changed-files compromise (CVE-2025-30066)](https://github.com/step-security/harden-runner#detected-attacks). |
|
|
73
|
+
| **Harden-Runner egress audit on every job** | [step-security/harden-runner](https://github.com/step-security/harden-runner) records every outbound network call, file write, and process spawn on every CI runner. Detects exfiltration attempts in real time — caught the tj-actions, NX, Shai-Hulud, and Axios attacks for other projects. |
|
|
74
|
+
| **OpenSSF Scorecard** | Weekly + on-push score of the repo's security posture (Pinned-Dependencies, Token-Permissions, Branch-Protection, Code-Review, Dangerous-Workflow, Maintained, SAST, Security-Policy, Signed-Releases, Vulnerabilities). Public results at [scorecard.dev](https://scorecard.dev/viewer/?uri=github.com/kensaurus/mushi-mushi). |
|
|
75
|
+
| **Server-side secret scan (Gitleaks)** | Every PR and every push to `master` runs Gitleaks across the diff / full tree. Belt-and-suspenders to the local pre-commit hook (`scripts/check-no-secrets.mjs`) which can be bypassed with `--no-verify`. |
|
|
76
|
+
| **Local pre-commit secret scanner** | `scripts/check-no-secrets.mjs` runs as a git hook installed by `pnpm install`, blocking commits that look like AWS / Stripe / GitHub / Anthropic / OpenAI / Slack / Supabase keys. |
|
|
77
|
+
| **CodeQL `security-extended`** | Semantic analysis of every TypeScript / JavaScript change finds injection sinks, taint flows, prototype pollution, etc. Runs on every PR, push, and weekly cron. |
|
|
78
|
+
| **Dependency review on PRs** | `actions/dependency-review-action` blocks the PR if it adds or upgrades a dep with a high-severity advisory. |
|
|
79
|
+
| **`pnpm audit --prod --audit-level=high`** | Weekly cron + every push to `master` fails on any high/critical advisory in production deps. |
|
|
80
|
+
|
|
81
|
+
### Install-time controls (protect the project's own dependency graph)
|
|
82
|
+
|
|
83
|
+
| Control | What it does |
|
|
84
|
+
|---|---|
|
|
85
|
+
| **`min-release-age` (npm) / `minimumReleaseAge` (pnpm)** | Refuses to resolve any dep version published less than 7 days ago. The Axios 1.14.1 / 0.30.4 compromise (Mar 2026) was detected and removed within ~5 hours; Shai-Hulud (Sep 2025) within <12 hours — a 7-day cooldown blocks every publicly-disclosed 2025–2026 npm supply-chain attack outright. |
|
|
86
|
+
| **`strictDepBuilds: true`** | Fails the install if any transitive dep tries to run a `postinstall` hook the workspace hasn't pre-approved (`onlyBuiltDependencies` allow-list). |
|
|
87
|
+
| **`blockExoticSubdeps: true`** | Refuses to resolve transitive deps from git URLs, tarball URLs, or filesystem paths — anything that didn't go through the npm registry's signing pipeline. |
|
|
88
|
+
| **Dependabot with cooldown** | Routine dep upgrades wait 7 days; security advisories bypass the cooldown automatically. |
|
|
89
|
+
| **`pnpm audit signatures`-style verification** | The release pipeline re-runs `npm audit signatures` against each published version after the publish, with `--audit-level=high`. |
|
|
90
|
+
|
|
91
|
+
### Verifying a Mushi Mushi tarball before installing
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# 1. Check provenance attestation matches the public GitHub Actions run
|
|
95
|
+
npm view @mushi-mushi/core --json | jq '.signatures, .dist'
|
|
96
|
+
|
|
97
|
+
# 2. Inside your own project after install
|
|
98
|
+
npm audit signatures
|
|
99
|
+
|
|
100
|
+
# Expected: every @mushi-mushi/* package reports
|
|
101
|
+
# "verified registry signature"
|
|
102
|
+
# "verified attestation"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
If `npm audit signatures` reports any `@mushi-mushi/*` package as unsigned
|
|
106
|
+
or with an invalid attestation, **stop the install and email
|
|
107
|
+
kensaurus@gmail.com immediately** — that's the symptom of either a
|
|
108
|
+
registry compromise or a tampered tarball, and we want to know within
|
|
109
|
+
hours, not days.
|
|
110
|
+
|
|
111
|
+
### What this hardening does NOT cover
|
|
112
|
+
|
|
113
|
+
- **Self-hosted deployments.** Once the package is on your machine, the
|
|
114
|
+
security of your `node_modules`, build pipeline, and runtime is your
|
|
115
|
+
responsibility. The hardening above protects the path from source to
|
|
116
|
+
registry; it cannot protect a tarball after it has been downloaded.
|
|
117
|
+
- **Compromise of `kensaurus@gmail.com`.** A trusted-publisher rule still
|
|
118
|
+
lets the legitimate maintainer publish from any branch they push. If
|
|
119
|
+
you find yourself with admin access to this repo, treat
|
|
120
|
+
`.github/workflows/release.yml` as a tier-0 secret.
|
|
121
|
+
- **First-party bugs.** Provenance proves *who* built the tarball and
|
|
122
|
+
*when*; it does not prove the code is bug-free. CodeQL + tests cover
|
|
123
|
+
that surface, but no automation catches everything — please continue
|
|
124
|
+
to report issues to the address above.
|
package/dist/index.cjs
CHANGED
|
@@ -523,7 +523,8 @@ var import_react_native3 = require("react-native");
|
|
|
523
523
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
524
524
|
var MushiFloatingButton = ({
|
|
525
525
|
onPress,
|
|
526
|
-
position = "bottom-right"
|
|
526
|
+
position = "bottom-right",
|
|
527
|
+
inset
|
|
527
528
|
}) => {
|
|
528
529
|
const scheme = (0, import_react_native3.useColorScheme)();
|
|
529
530
|
const dark = scheme === "dark";
|
|
@@ -544,7 +545,7 @@ var MushiFloatingButton = ({
|
|
|
544
545
|
stiffness: 250
|
|
545
546
|
}).start();
|
|
546
547
|
}, [scale]);
|
|
547
|
-
const positionStyle = position === "bottom-left" ? { left: 20 } : { right: 20 };
|
|
548
|
+
const positionStyle = position === "bottom-left" ? { left: inset?.left ?? 20 } : { right: inset?.right ?? 20 };
|
|
548
549
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
549
550
|
import_react_native3.TouchableWithoutFeedback,
|
|
550
551
|
{
|
|
@@ -557,6 +558,7 @@ var MushiFloatingButton = ({
|
|
|
557
558
|
style: [
|
|
558
559
|
s2.fab,
|
|
559
560
|
positionStyle,
|
|
561
|
+
{ bottom: inset?.bottom ?? (import_react_native3.Platform.OS === "ios" ? 50 : 28) },
|
|
560
562
|
{
|
|
561
563
|
backgroundColor: dark ? "#0a84ff" : "#007aff",
|
|
562
564
|
transform: [{ scale }],
|
|
@@ -572,7 +574,6 @@ var MushiFloatingButton = ({
|
|
|
572
574
|
var s2 = import_react_native3.StyleSheet.create({
|
|
573
575
|
fab: {
|
|
574
576
|
position: "absolute",
|
|
575
|
-
bottom: import_react_native3.Platform.OS === "ios" ? 50 : 28,
|
|
576
577
|
width: 56,
|
|
577
578
|
height: 56,
|
|
578
579
|
borderRadius: 28,
|
|
@@ -625,6 +626,33 @@ function MushiProvider({ children, ...config }) {
|
|
|
625
626
|
}, []);
|
|
626
627
|
const open = (0, import_react3.useCallback)(() => setSheetVisible(true), []);
|
|
627
628
|
const close = (0, import_react3.useCallback)(() => setSheetVisible(false), []);
|
|
629
|
+
const attachTo = (0, import_react3.useCallback)(() => ({ onPress: open }), [open]);
|
|
630
|
+
(0, import_react3.useEffect)(() => {
|
|
631
|
+
const trigger2 = config.widget?.trigger ?? "button";
|
|
632
|
+
if (trigger2 !== "shake" && trigger2 !== "both") return;
|
|
633
|
+
let disposed = false;
|
|
634
|
+
let cleanup;
|
|
635
|
+
let lastShake = 0;
|
|
636
|
+
const threshold = config.widget?.shakeThreshold ?? 2.7;
|
|
637
|
+
new Function("specifier", "return import(specifier)")("expo-sensors").then((mod) => {
|
|
638
|
+
if (disposed) return;
|
|
639
|
+
mod.Accelerometer.setUpdateInterval(120);
|
|
640
|
+
const sub = mod.Accelerometer.addListener((evt) => {
|
|
641
|
+
const g = Math.sqrt(evt.x * evt.x + evt.y * evt.y + evt.z * evt.z);
|
|
642
|
+
if (g < threshold) return;
|
|
643
|
+
const now = Date.now();
|
|
644
|
+
if (now - lastShake < 1e3) return;
|
|
645
|
+
lastShake = now;
|
|
646
|
+
open();
|
|
647
|
+
});
|
|
648
|
+
cleanup = () => sub.remove();
|
|
649
|
+
}).catch(() => {
|
|
650
|
+
});
|
|
651
|
+
return () => {
|
|
652
|
+
disposed = true;
|
|
653
|
+
cleanup?.();
|
|
654
|
+
};
|
|
655
|
+
}, [config.widget?.trigger, config.widget?.shakeThreshold, open]);
|
|
628
656
|
const submitReport = (0, import_react3.useCallback)(
|
|
629
657
|
async (data) => {
|
|
630
658
|
const deviceInfo = getDeviceInfo();
|
|
@@ -664,22 +692,24 @@ function MushiProvider({ children, ...config }) {
|
|
|
664
692
|
() => ({
|
|
665
693
|
open,
|
|
666
694
|
close,
|
|
695
|
+
attachTo,
|
|
667
696
|
submitReport,
|
|
668
697
|
getDeviceInfo,
|
|
669
698
|
getConsoleEntries: () => consoleRef.current?.getEntries() ?? [],
|
|
670
699
|
getNetworkEntries: () => networkRef.current?.getEntries() ?? []
|
|
671
700
|
}),
|
|
672
|
-
[open, close, submitReport]
|
|
701
|
+
[open, close, attachTo, submitReport]
|
|
673
702
|
);
|
|
674
703
|
const trigger = config.widget?.trigger ?? "button";
|
|
675
|
-
const showFab = trigger === "button" || trigger === "both";
|
|
704
|
+
const showFab = trigger === "button" || trigger === "both" || trigger === "auto" || trigger === "edge-tab";
|
|
676
705
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(MushiContext.Provider, { value: instance, children: [
|
|
677
706
|
children,
|
|
678
707
|
showFab && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
679
708
|
MushiFloatingButton,
|
|
680
709
|
{
|
|
681
710
|
onPress: open,
|
|
682
|
-
position: config.widget?.buttonPosition
|
|
711
|
+
position: config.widget?.buttonPosition,
|
|
712
|
+
inset: config.widget?.inset
|
|
683
713
|
}
|
|
684
714
|
),
|
|
685
715
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MushiBottomSheet, { visible: sheetVisible, onClose: close })
|
package/dist/index.d.cts
CHANGED
|
@@ -44,9 +44,14 @@ interface MushiRNConfig {
|
|
|
44
44
|
apiKey: string;
|
|
45
45
|
endpoint?: string;
|
|
46
46
|
widget?: {
|
|
47
|
-
trigger?: 'shake' | 'button' | 'both' | 'manual';
|
|
47
|
+
trigger?: 'shake' | 'button' | 'both' | 'manual' | 'auto' | 'edge-tab' | 'hidden' | 'attach';
|
|
48
48
|
shakeThreshold?: number;
|
|
49
49
|
buttonPosition?: 'bottom-right' | 'bottom-left';
|
|
50
|
+
inset?: {
|
|
51
|
+
bottom?: number;
|
|
52
|
+
left?: number;
|
|
53
|
+
right?: number;
|
|
54
|
+
};
|
|
50
55
|
};
|
|
51
56
|
capture?: {
|
|
52
57
|
console?: boolean;
|
|
@@ -63,6 +68,9 @@ interface MushiRNConfig {
|
|
|
63
68
|
interface MushiRNInstance {
|
|
64
69
|
open(): void;
|
|
65
70
|
close(): void;
|
|
71
|
+
attachTo(): {
|
|
72
|
+
onPress: () => void;
|
|
73
|
+
};
|
|
66
74
|
submitReport(data: {
|
|
67
75
|
description: string;
|
|
68
76
|
category: string;
|
|
@@ -150,6 +158,11 @@ declare const MushiBottomSheet: FC<MushiBottomSheetProps>;
|
|
|
150
158
|
interface MushiFloatingButtonProps {
|
|
151
159
|
onPress: () => void;
|
|
152
160
|
position?: 'bottom-right' | 'bottom-left';
|
|
161
|
+
inset?: {
|
|
162
|
+
bottom?: number;
|
|
163
|
+
left?: number;
|
|
164
|
+
right?: number;
|
|
165
|
+
};
|
|
153
166
|
}
|
|
154
167
|
declare const MushiFloatingButton: FC<MushiFloatingButtonProps>;
|
|
155
168
|
|
package/dist/index.d.ts
CHANGED
|
@@ -44,9 +44,14 @@ interface MushiRNConfig {
|
|
|
44
44
|
apiKey: string;
|
|
45
45
|
endpoint?: string;
|
|
46
46
|
widget?: {
|
|
47
|
-
trigger?: 'shake' | 'button' | 'both' | 'manual';
|
|
47
|
+
trigger?: 'shake' | 'button' | 'both' | 'manual' | 'auto' | 'edge-tab' | 'hidden' | 'attach';
|
|
48
48
|
shakeThreshold?: number;
|
|
49
49
|
buttonPosition?: 'bottom-right' | 'bottom-left';
|
|
50
|
+
inset?: {
|
|
51
|
+
bottom?: number;
|
|
52
|
+
left?: number;
|
|
53
|
+
right?: number;
|
|
54
|
+
};
|
|
50
55
|
};
|
|
51
56
|
capture?: {
|
|
52
57
|
console?: boolean;
|
|
@@ -63,6 +68,9 @@ interface MushiRNConfig {
|
|
|
63
68
|
interface MushiRNInstance {
|
|
64
69
|
open(): void;
|
|
65
70
|
close(): void;
|
|
71
|
+
attachTo(): {
|
|
72
|
+
onPress: () => void;
|
|
73
|
+
};
|
|
66
74
|
submitReport(data: {
|
|
67
75
|
description: string;
|
|
68
76
|
category: string;
|
|
@@ -150,6 +158,11 @@ declare const MushiBottomSheet: FC<MushiBottomSheetProps>;
|
|
|
150
158
|
interface MushiFloatingButtonProps {
|
|
151
159
|
onPress: () => void;
|
|
152
160
|
position?: 'bottom-right' | 'bottom-left';
|
|
161
|
+
inset?: {
|
|
162
|
+
bottom?: number;
|
|
163
|
+
left?: number;
|
|
164
|
+
right?: number;
|
|
165
|
+
};
|
|
153
166
|
}
|
|
154
167
|
declare const MushiFloatingButton: FC<MushiFloatingButtonProps>;
|
|
155
168
|
|
package/dist/index.js
CHANGED
|
@@ -509,7 +509,8 @@ import {
|
|
|
509
509
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
510
510
|
var MushiFloatingButton = ({
|
|
511
511
|
onPress,
|
|
512
|
-
position = "bottom-right"
|
|
512
|
+
position = "bottom-right",
|
|
513
|
+
inset
|
|
513
514
|
}) => {
|
|
514
515
|
const scheme = useColorScheme2();
|
|
515
516
|
const dark = scheme === "dark";
|
|
@@ -530,7 +531,7 @@ var MushiFloatingButton = ({
|
|
|
530
531
|
stiffness: 250
|
|
531
532
|
}).start();
|
|
532
533
|
}, [scale]);
|
|
533
|
-
const positionStyle = position === "bottom-left" ? { left: 20 } : { right: 20 };
|
|
534
|
+
const positionStyle = position === "bottom-left" ? { left: inset?.left ?? 20 } : { right: inset?.right ?? 20 };
|
|
534
535
|
return /* @__PURE__ */ jsx2(
|
|
535
536
|
TouchableWithoutFeedback,
|
|
536
537
|
{
|
|
@@ -543,6 +544,7 @@ var MushiFloatingButton = ({
|
|
|
543
544
|
style: [
|
|
544
545
|
s2.fab,
|
|
545
546
|
positionStyle,
|
|
547
|
+
{ bottom: inset?.bottom ?? (Platform3.OS === "ios" ? 50 : 28) },
|
|
546
548
|
{
|
|
547
549
|
backgroundColor: dark ? "#0a84ff" : "#007aff",
|
|
548
550
|
transform: [{ scale }],
|
|
@@ -558,7 +560,6 @@ var MushiFloatingButton = ({
|
|
|
558
560
|
var s2 = StyleSheet2.create({
|
|
559
561
|
fab: {
|
|
560
562
|
position: "absolute",
|
|
561
|
-
bottom: Platform3.OS === "ios" ? 50 : 28,
|
|
562
563
|
width: 56,
|
|
563
564
|
height: 56,
|
|
564
565
|
borderRadius: 28,
|
|
@@ -611,6 +612,33 @@ function MushiProvider({ children, ...config }) {
|
|
|
611
612
|
}, []);
|
|
612
613
|
const open = useCallback3(() => setSheetVisible(true), []);
|
|
613
614
|
const close = useCallback3(() => setSheetVisible(false), []);
|
|
615
|
+
const attachTo = useCallback3(() => ({ onPress: open }), [open]);
|
|
616
|
+
useEffect2(() => {
|
|
617
|
+
const trigger2 = config.widget?.trigger ?? "button";
|
|
618
|
+
if (trigger2 !== "shake" && trigger2 !== "both") return;
|
|
619
|
+
let disposed = false;
|
|
620
|
+
let cleanup;
|
|
621
|
+
let lastShake = 0;
|
|
622
|
+
const threshold = config.widget?.shakeThreshold ?? 2.7;
|
|
623
|
+
new Function("specifier", "return import(specifier)")("expo-sensors").then((mod) => {
|
|
624
|
+
if (disposed) return;
|
|
625
|
+
mod.Accelerometer.setUpdateInterval(120);
|
|
626
|
+
const sub = mod.Accelerometer.addListener((evt) => {
|
|
627
|
+
const g = Math.sqrt(evt.x * evt.x + evt.y * evt.y + evt.z * evt.z);
|
|
628
|
+
if (g < threshold) return;
|
|
629
|
+
const now = Date.now();
|
|
630
|
+
if (now - lastShake < 1e3) return;
|
|
631
|
+
lastShake = now;
|
|
632
|
+
open();
|
|
633
|
+
});
|
|
634
|
+
cleanup = () => sub.remove();
|
|
635
|
+
}).catch(() => {
|
|
636
|
+
});
|
|
637
|
+
return () => {
|
|
638
|
+
disposed = true;
|
|
639
|
+
cleanup?.();
|
|
640
|
+
};
|
|
641
|
+
}, [config.widget?.trigger, config.widget?.shakeThreshold, open]);
|
|
614
642
|
const submitReport = useCallback3(
|
|
615
643
|
async (data) => {
|
|
616
644
|
const deviceInfo = getDeviceInfo();
|
|
@@ -650,22 +678,24 @@ function MushiProvider({ children, ...config }) {
|
|
|
650
678
|
() => ({
|
|
651
679
|
open,
|
|
652
680
|
close,
|
|
681
|
+
attachTo,
|
|
653
682
|
submitReport,
|
|
654
683
|
getDeviceInfo,
|
|
655
684
|
getConsoleEntries: () => consoleRef.current?.getEntries() ?? [],
|
|
656
685
|
getNetworkEntries: () => networkRef.current?.getEntries() ?? []
|
|
657
686
|
}),
|
|
658
|
-
[open, close, submitReport]
|
|
687
|
+
[open, close, attachTo, submitReport]
|
|
659
688
|
);
|
|
660
689
|
const trigger = config.widget?.trigger ?? "button";
|
|
661
|
-
const showFab = trigger === "button" || trigger === "both";
|
|
690
|
+
const showFab = trigger === "button" || trigger === "both" || trigger === "auto" || trigger === "edge-tab";
|
|
662
691
|
return /* @__PURE__ */ jsxs2(MushiContext.Provider, { value: instance, children: [
|
|
663
692
|
children,
|
|
664
693
|
showFab && /* @__PURE__ */ jsx3(
|
|
665
694
|
MushiFloatingButton,
|
|
666
695
|
{
|
|
667
696
|
onPress: open,
|
|
668
|
-
position: config.widget?.buttonPosition
|
|
697
|
+
position: config.widget?.buttonPosition,
|
|
698
|
+
inset: config.widget?.inset
|
|
669
699
|
}
|
|
670
700
|
),
|
|
671
701
|
/* @__PURE__ */ jsx3(MushiBottomSheet, { visible: sheetVisible, onClose: close })
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mushi-mushi/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "React Native SDK for Mushi Mushi — shake-to-report, bottom sheet widget, full context capture",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"peerDependencies": {
|
|
10
10
|
"@react-native-async-storage/async-storage": ">=1.19",
|
|
11
11
|
"@react-navigation/native": ">=6",
|
|
12
|
+
"expo-sensors": ">=13",
|
|
12
13
|
"react": ">=18",
|
|
13
14
|
"react-native": ">=0.72"
|
|
14
15
|
},
|
|
@@ -18,10 +19,13 @@
|
|
|
18
19
|
},
|
|
19
20
|
"@react-native-async-storage/async-storage": {
|
|
20
21
|
"optional": true
|
|
22
|
+
},
|
|
23
|
+
"expo-sensors": {
|
|
24
|
+
"optional": true
|
|
21
25
|
}
|
|
22
26
|
},
|
|
23
27
|
"dependencies": {
|
|
24
|
-
"@mushi-mushi/core": "^0.
|
|
28
|
+
"@mushi-mushi/core": "^0.7.0"
|
|
25
29
|
},
|
|
26
30
|
"devDependencies": {
|
|
27
31
|
"@types/react": "^19.1.4",
|