Skip to content

Commit cc06526

Browse files
authored
fix: use URL polyfill for websocket bridge (#99)
1 parent 3eda0bc commit cc06526

File tree

4 files changed

+103
-3
lines changed

4 files changed

+103
-3
lines changed

packages/runtime/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"@vitest/spy": "4.0.16",
4646
"chai": "^6.2.2",
4747
"event-target-shim": "^6.0.2",
48+
"react-native-url-polyfill": "^3.0.0",
4849
"use-sync-external-store": "^1.6.0",
4950
"zustand": "^5.0.5"
5051
},
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { HARNESS_BRIDGE_PATH } from '@react-native-harness/bridge';
2+
import { beforeEach, describe, expect, it, vi } from 'vitest';
3+
import { getWSServer } from './getWSServer.js';
4+
5+
const mocks = vi.hoisted(() => ({
6+
getDevServerUrl: vi.fn(),
7+
}));
8+
9+
vi.mock('../utils/dev-server.js', () => ({
10+
getDevServerUrl: mocks.getDevServerUrl,
11+
}));
12+
13+
vi.mock('react-native-url-polyfill', () => ({
14+
URL,
15+
}));
16+
17+
describe('getWSServer', () => {
18+
beforeEach(() => {
19+
mocks.getDevServerUrl.mockReset();
20+
});
21+
22+
it('builds a websocket bridge URL from an http dev server URL', () => {
23+
mocks.getDevServerUrl.mockReturnValue(
24+
'http://localhost:8081/index.bundle?platform=ios&dev=true#main',
25+
);
26+
27+
expect(getWSServer()).toBe(`ws://localhost:8081${HARNESS_BRIDGE_PATH}`);
28+
});
29+
30+
it('builds a secure websocket bridge URL from an https dev server URL', () => {
31+
mocks.getDevServerUrl.mockReturnValue('HTTPS://Example.COM:19000/');
32+
33+
expect(getWSServer()).toBe(`wss://example.com:19000${HARNESS_BRIDGE_PATH}`);
34+
});
35+
36+
it('preserves the explicit port for hostnames', () => {
37+
mocks.getDevServerUrl.mockReturnValue('http://example.com:31337/status');
38+
39+
expect(getWSServer()).toBe(`ws://example.com:31337${HARNESS_BRIDGE_PATH}`);
40+
});
41+
42+
it('drops user info while preserving the host for ipv6 URLs', () => {
43+
mocks.getDevServerUrl.mockReturnValue(
44+
'http://user:secret@[::1]:8081/status',
45+
);
46+
47+
expect(getWSServer()).toBe(`ws://[::1]:8081${HARNESS_BRIDGE_PATH}`);
48+
});
49+
50+
it('preserves the port for ipv6 URLs without user info', () => {
51+
mocks.getDevServerUrl.mockReturnValue('http://[2001:db8::1]:19001/status');
52+
53+
expect(getWSServer()).toBe(
54+
`ws://[2001:db8::1]:19001${HARNESS_BRIDGE_PATH}`,
55+
);
56+
});
57+
58+
it('throws for non-absolute dev server URLs', () => {
59+
mocks.getDevServerUrl.mockReturnValue('localhost:8081');
60+
61+
expect(() => getWSServer()).toThrow(
62+
new TypeError('Invalid URL: localhost:8081'),
63+
);
64+
});
65+
});

packages/runtime/src/client/getWSServer.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import { HARNESS_BRIDGE_PATH } from '@react-native-harness/bridge';
2+
import { URL } from 'react-native-url-polyfill';
23
import { getDevServerUrl } from '../utils/dev-server.js';
34

45
export const getWSServer = (): string => {
5-
const devServerUrl = new URL(getDevServerUrl());
6+
const devServerUrlString = getDevServerUrl();
7+
const devServerUrl = new URL(devServerUrlString);
8+
9+
if (!devServerUrl.host) {
10+
throw new TypeError(`Invalid URL: ${devServerUrlString}`);
11+
}
12+
613
const protocol = devServerUrl.protocol === 'https:' ? 'wss:' : 'ws:';
714

815
return `${protocol}//${devServerUrl.host}${HARNESS_BRIDGE_PATH}`;

pnpm-lock.yaml

Lines changed: 29 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)