Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/onmessage-callback-error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@callstack/react-native-brownfield': patch
---

`ReactNativeBrownfield.onMessage` no longer calls the callback a second time, with the raw message text, when the callback throws. The error now propagates instead of being swallowed.
73 changes: 73 additions & 0 deletions packages/react-native-brownfield/src/__tests__/onMessage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';

const native = vi.hoisted(() => ({
listener: undefined as ((payload: { text: string }) => void) | undefined,
remove: vi.fn(),
}));

vi.mock('react-native', () => ({
Platform: { OS: 'ios' },
NativeModules: {},
}));
vi.mock('../NativeReactNativeBrownfieldModule', () => ({
default: {
onBrownfieldMessage: (listener: (payload: { text: string }) => void) => {
native.listener = listener;
return { remove: native.remove };
},
},
}));

import ReactNativeBrownfield from '../index';

function emit(text: string) {
native.listener?.({ text });
}

describe('onMessage', () => {
beforeEach(() => {
native.listener = undefined;
native.remove.mockClear();
});

it('passes parsed JSON to the callback', () => {
const callback = vi.fn();
ReactNativeBrownfield.onMessage(callback);

emit('{"text":"hello","id":1}');

expect(callback).toHaveBeenCalledExactlyOnceWith({
data: { text: 'hello', id: 1 },
});
});

it('passes the raw text when the message is not valid JSON', () => {
const callback = vi.fn();
ReactNativeBrownfield.onMessage(callback);

emit('not json');

expect(callback).toHaveBeenCalledExactlyOnceWith({ data: 'not json' });
});

it('does not call the callback again when it throws', () => {
const error = new Error('callback failed');
const callback = vi.fn(() => {
throw error;
});
ReactNativeBrownfield.onMessage(callback);

expect(() => emit('{"text":"hello"}')).toThrow(error);
expect(callback).toHaveBeenCalledExactlyOnceWith({
data: { text: 'hello' },
});
});

it('removes the native subscription', () => {
const subscription = ReactNativeBrownfield.onMessage(vi.fn());

subscription.remove();

expect(native.remove).toHaveBeenCalledOnce();
});
});
6 changes: 4 additions & 2 deletions packages/react-native-brownfield/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,13 @@ const ReactNativeBrownfield = {
): { remove: () => void } => {
const subscription = ReactNativeBrownfieldModule.onBrownfieldMessage(
(payload) => {
let data: unknown;
try {
callback({ data: JSON.parse(payload.text) });
data = JSON.parse(payload.text);
} catch {
callback({ data: payload.text });
data = payload.text;
}
callback({ data });
}
);
return { remove: () => subscription.remove() };
Expand Down