From 6ff9c8657d1698de3fe3e515eb004a7a4d7c525d Mon Sep 17 00:00:00 2001 From: Bobby Galli Date: Sat, 18 Apr 2026 12:12:08 -0400 Subject: [PATCH 1/3] fix: remove componentStack attachment override mechanism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The setCreateComponentStackAttachment / getCreateComponentStackAttachment on Scope was added to work around RN's Blob not serializing in FormData. The real fix is expo-blob (a web-standards Blob polyfill for RN), which makes new Blob() work correctly with FormData + fetch on native. With expo-blob handling the polyfill in @bugsplat/expo, bugsplat-react can use plain new Blob() everywhere — no override mechanism needed. Co-Authored-By: Claude Opus 4.7 (1M context) --- spec/ErrorBoundary.spec.tsx | 21 +-------------------- src/ErrorBoundary.tsx | 10 ++++------ src/index.ts | 1 - src/scope.ts | 37 +++---------------------------------- 4 files changed, 8 insertions(+), 61 deletions(-) diff --git a/spec/ErrorBoundary.spec.tsx b/spec/ErrorBoundary.spec.tsx index fc978fd..9eee908 100644 --- a/spec/ErrorBoundary.spec.tsx +++ b/spec/ErrorBoundary.spec.tsx @@ -152,7 +152,7 @@ describe('', () => { await waitFor(() => expect(mockPost).toHaveBeenCalledTimes(1)); }); - it('should attach componentStack as a text/plain Blob by default', async () => { + it('should attach componentStack as a Blob', async () => { render( @@ -166,25 +166,6 @@ describe('', () => { const [attachment] = options.attachments; expect(attachment.filename).toBe('componentStack.txt'); expect(attachment.data).toBeInstanceOf(Blob); - expect(attachment.data.type).toBe('text/plain'); - }); - - it('honors scope.getCreateComponentStackAttachment() when provided', async () => { - const customAttachment = { filename: 'componentStack.txt', data: 'CUSTOM' }; - const customBuilder = jest.fn(() => customAttachment); - const scopeWithBuilder = new Scope(bugSplat, customBuilder); - - render( - - - - ); - - await waitFor(() => expect(mockPost).toHaveBeenCalledTimes(1)); - - expect(customBuilder).toHaveBeenCalledWith(expect.stringContaining('BlowUp')); - const [, options] = mockPost.mock.calls[0]; - expect(options.attachments).toEqual([customAttachment]); }); it('should call beforePost', async () => { diff --git a/src/ErrorBoundary.tsx b/src/ErrorBoundary.tsx index 770a74c..5b7f2be 100644 --- a/src/ErrorBoundary.tsx +++ b/src/ErrorBoundary.tsx @@ -1,6 +1,5 @@ import { type BugSplat, - type BugSplatAttachment, type BugSplatResponse, } from 'bugsplat'; import { @@ -11,8 +10,7 @@ import { type ReactElement, type ReactNode, } from 'react'; -import { appScope } from './appScope'; -import type { Scope } from './scope'; +import { getBugSplat } from './appScope'; /** * Shallowly compare two arrays to determine if they are different. @@ -139,7 +137,7 @@ interface InternalErrorBoundaryProps { * to pass their own scope that will inject the client for use by * ErrorBoundary. */ - scope: Pick; + scope: { getClient(): BugSplat | null }; } export type ErrorBoundaryProps = JSX.LibraryManagedAttributes< @@ -199,7 +197,7 @@ export class ErrorBoundary extends Component< onResetKeysChange: noop, onUnmount: noop, disablePost: false, - scope: appScope, + scope: { getClient: getBugSplat }, }; state = INITIAL_STATE; @@ -247,7 +245,7 @@ export class ErrorBoundary extends Component< return client.post(error, { attachments: componentStack - ? [scope.getCreateComponentStackAttachment()(componentStack)] + ? [{ filename: 'componentStack.txt', data: new Blob([componentStack]) }] : [], }); } diff --git a/src/index.ts b/src/index.ts index da28709..13832a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,6 @@ export type { export * from './appScope'; export * from './ErrorBoundary'; -export * from './scope'; export * from './useErrorHandler'; export * from './useFeedback'; export * from './withErrorBoundary'; diff --git a/src/scope.ts b/src/scope.ts index 3b303ca..214c01c 100644 --- a/src/scope.ts +++ b/src/scope.ts @@ -1,30 +1,10 @@ -import type { BugSplat, BugSplatAttachment } from 'bugsplat'; +import type { BugSplat } from 'bugsplat'; /** - * Builds the componentStack attachment for ErrorBoundary posts. - */ -export type CreateComponentStackAttachment = ( - componentStack: string -) => BugSplatAttachment; - -/** - * Default builder — wraps the stack in a `text/plain` `Blob`. - */ -export const defaultCreateComponentStackAttachment: CreateComponentStackAttachment = ( - componentStack -) => ({ - filename: 'componentStack.txt', - data: new Blob([componentStack], { type: 'text/plain' }), -}); - -/** - * Encapsulate BugSplat client instance and scope-level overrides. + * Encapsulate BugSplat client instance */ export class Scope { - constructor( - private client: BugSplat | null = null, - private createComponentStackAttachment: CreateComponentStackAttachment = defaultCreateComponentStackAttachment - ) {} + constructor(private client: BugSplat | null = null) {} /** * @returns BugSplat client instance or null if unset @@ -36,15 +16,4 @@ export class Scope { setClient(client: BugSplat) { this.client = client; } - - /** - * @returns the current componentStack attachment builder - */ - getCreateComponentStackAttachment() { - return this.createComponentStackAttachment; - } - - setCreateComponentStackAttachment(fn: CreateComponentStackAttachment) { - this.createComponentStackAttachment = fn; - } } From 198beab6d61a10266b0027188408556e38f9bbee Mon Sep 17 00:00:00 2001 From: Bobby Galli Date: Sat, 18 Apr 2026 13:28:17 -0400 Subject: [PATCH 2/3] chore: bump bugsplat dependency to ^9.1.1 Requires the version with injectable _fetch, needed by @bugsplat/expo to inject expo/fetch for React Native Blob support. Co-Authored-By: Claude Opus 4.7 (1M context) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ae827be..e273be2 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "author": "zmrl", "license": "MIT", "dependencies": { - "bugsplat": "^9.1.0" + "bugsplat": "^9.1.1" }, "devDependencies": { "@bugsplat/js-api-client": "^2.0.0", From 57e8d8445daace5ee44f3f49ed2b4f39283c941f Mon Sep 17 00:00:00 2001 From: Bobby Galli Date: Sat, 18 Apr 2026 14:11:06 -0400 Subject: [PATCH 3/3] fix: add text/plain MIME type to componentStack Blob Also strengthens test assertion to verify the MIME type. Co-Authored-By: Claude Opus 4.7 (1M context) --- package-lock.json | 8 ++++---- spec/ErrorBoundary.spec.tsx | 1 + src/ErrorBoundary.tsx | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7e25e61..319ccc8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "examples/*" ], "dependencies": { - "bugsplat": "^9.1.0" + "bugsplat": "^9.1.1" }, "devDependencies": { "@bugsplat/js-api-client": "^2.0.0", @@ -4154,9 +4154,9 @@ "license": "MIT" }, "node_modules/bugsplat": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/bugsplat/-/bugsplat-9.1.0.tgz", - "integrity": "sha512-QIfY6kMtlMwJQz2Beg42QJ65TgkKP49jv5zBVCX2X86CDdCRFY0i/B9s1k3aqElJMRLVkiWxUut6FRTn2Xx5fQ==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bugsplat/-/bugsplat-9.1.1.tgz", + "integrity": "sha512-uAzCh6lVhfDFctIdXOMlqv4HUsbePDrvLX87KcC6zC8YaZ2SSZfvRxeFJNnoUc4aui0aqY3uCMKSz1lo5x0oxg==", "license": "MIT", "engines": { "node": ">=16.0.0", diff --git a/spec/ErrorBoundary.spec.tsx b/spec/ErrorBoundary.spec.tsx index 9eee908..63dedad 100644 --- a/spec/ErrorBoundary.spec.tsx +++ b/spec/ErrorBoundary.spec.tsx @@ -166,6 +166,7 @@ describe('', () => { const [attachment] = options.attachments; expect(attachment.filename).toBe('componentStack.txt'); expect(attachment.data).toBeInstanceOf(Blob); + expect(attachment.data.type).toBe('text/plain'); }); it('should call beforePost', async () => { diff --git a/src/ErrorBoundary.tsx b/src/ErrorBoundary.tsx index 5b7f2be..0d3ca4c 100644 --- a/src/ErrorBoundary.tsx +++ b/src/ErrorBoundary.tsx @@ -245,7 +245,7 @@ export class ErrorBoundary extends Component< return client.post(error, { attachments: componentStack - ? [{ filename: 'componentStack.txt', data: new Blob([componentStack]) }] + ? [{ filename: 'componentStack.txt', data: new Blob([componentStack], { type: 'text/plain' }) }] : [], }); }