expo 54.0.0-canary-20250729-d8899ae → 54.0.0-preview.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/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/ExpoReactHostFactory.kt +1 -7
- package/android/src/main/java/expo/modules/ReactActivityDelegateWrapper.kt +8 -8
- package/android/src/main/java/expo/modules/ReactNativeHostWrapper.kt +0 -5
- package/android/src/test/resources/robolectric.properties +1 -0
- package/build/Expo.d.ts +9 -1
- package/build/Expo.d.ts.map +1 -1
- package/build/async-require/fetchAsync.native.d.ts.map +1 -1
- package/build/winter/installGlobal.d.ts +25 -0
- package/build/winter/installGlobal.d.ts.map +1 -0
- package/bundledNativeModules.json +94 -93
- package/devtools.d.ts +1 -1
- package/devtools.js +1 -1
- package/internal/babel-preset.d.ts +2 -0
- package/internal/babel-preset.js +2 -0
- package/internal/unstable-autolinking-exports.d.ts +2 -0
- package/internal/unstable-autolinking-exports.js +2 -0
- package/internal/unstable-expo-updates-cli-exports.d.ts +2 -0
- package/internal/unstable-expo-updates-cli-exports.js +2 -0
- package/ios/AppDelegates/ExpoReactNativeFactory.swift +1 -1
- package/ios/AppDelegates/ExpoReactNativeFactoryDelegate.swift +1 -1
- package/ios/AppDelegates/RCTAppDelegateUmbrella.h +5 -0
- package/package.json +27 -25
- package/src/Expo.ts +4 -0
- package/src/async-require/fetchAsync.native.ts +48 -10
- package/src/async-require/messageSocket.native.ts +0 -3
- package/src/winter/installGlobal.ts +109 -0
- package/src/winter/runtime.native.ts +1 -20
- package/template.tgz +0 -0
- package/build/devtools/DevToolsPluginClient.d.ts +0 -72
- package/build/devtools/DevToolsPluginClient.d.ts.map +0 -1
- package/build/devtools/DevToolsPluginClientFactory.d.ts +0 -16
- package/build/devtools/DevToolsPluginClientFactory.d.ts.map +0 -1
- package/build/devtools/DevToolsPluginClientImplApp.d.ts +0 -15
- package/build/devtools/DevToolsPluginClientImplApp.d.ts.map +0 -1
- package/build/devtools/DevToolsPluginClientImplBrowser.d.ts +0 -14
- package/build/devtools/DevToolsPluginClientImplBrowser.d.ts.map +0 -1
- package/build/devtools/MessageFramePacker.d.ts +0 -50
- package/build/devtools/MessageFramePacker.d.ts.map +0 -1
- package/build/devtools/ProtocolVersion.d.ts +0 -7
- package/build/devtools/ProtocolVersion.d.ts.map +0 -1
- package/build/devtools/WebSocketBackingStore.d.ts +0 -10
- package/build/devtools/WebSocketBackingStore.d.ts.map +0 -1
- package/build/devtools/WebSocketWithReconnect.d.ts +0 -81
- package/build/devtools/WebSocketWithReconnect.d.ts.map +0 -1
- package/build/devtools/devtools.types.d.ts +0 -42
- package/build/devtools/devtools.types.d.ts.map +0 -1
- package/build/devtools/getConnectionInfo.d.ts +0 -6
- package/build/devtools/getConnectionInfo.d.ts.map +0 -1
- package/build/devtools/getConnectionInfo.native.d.ts +0 -6
- package/build/devtools/getConnectionInfo.native.d.ts.map +0 -1
- package/build/devtools/index.d.ts +0 -12
- package/build/devtools/index.d.ts.map +0 -1
- package/build/devtools/logger.d.ts +0 -6
- package/build/devtools/logger.d.ts.map +0 -1
- package/src/devtools/DevToolsPluginClient.ts +0 -240
- package/src/devtools/DevToolsPluginClientFactory.ts +0 -73
- package/src/devtools/DevToolsPluginClientImplApp.ts +0 -56
- package/src/devtools/DevToolsPluginClientImplBrowser.ts +0 -38
- package/src/devtools/MessageFramePacker.ts +0 -235
- package/src/devtools/ProtocolVersion.ts +0 -6
- package/src/devtools/WebSocketBackingStore.ts +0 -10
- package/src/devtools/WebSocketWithReconnect.ts +0 -318
- package/src/devtools/__tests__/DevToolsPluginClient-test.ts +0 -285
- package/src/devtools/__tests__/DevToolsPluginClientFactory-test.ts +0 -120
- package/src/devtools/__tests__/MessageFramePack-test.node.ts +0 -157
- package/src/devtools/__tests__/MockWebSocket.ts +0 -100
- package/src/devtools/__tests__/WebSocketWithReconnect-test.node.ts +0 -184
- package/src/devtools/__tests__/logger-test.ts +0 -20
- package/src/devtools/devtools.types.ts +0 -50
- package/src/devtools/getConnectionInfo.native.ts +0 -18
- package/src/devtools/getConnectionInfo.ts +0 -16
- package/src/devtools/index.ts +0 -53
- package/src/devtools/logger.ts +0 -29
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
|
|
3
|
-
import { TextDecoder, TextEncoder } from 'node:util';
|
|
4
|
-
|
|
5
|
-
import MockWebSocket from './MockWebSocket';
|
|
6
|
-
import {
|
|
7
|
-
cleanupDevToolsPluginInstances,
|
|
8
|
-
getDevToolsPluginClientAsync,
|
|
9
|
-
} from '../DevToolsPluginClientFactory';
|
|
10
|
-
import { DevToolsPluginClientImplApp } from '../DevToolsPluginClientImplApp';
|
|
11
|
-
import { DevToolsPluginClientImplBrowser } from '../DevToolsPluginClientImplBrowser';
|
|
12
|
-
import { getConnectionInfo } from '../getConnectionInfo';
|
|
13
|
-
|
|
14
|
-
jest.mock('../getConnectionInfo');
|
|
15
|
-
|
|
16
|
-
// @ts-expect-error - We don't mock all properties from WebSocket
|
|
17
|
-
globalThis.WebSocket = MockWebSocket;
|
|
18
|
-
|
|
19
|
-
// @ts-ignore - TextDecoder and TextEncoder are not defined in native jest environments.
|
|
20
|
-
globalThis.TextDecoder ??= TextDecoder;
|
|
21
|
-
globalThis.TextEncoder ??= TextEncoder;
|
|
22
|
-
|
|
23
|
-
const TEST_PROTOCOL_VERSION = 1;
|
|
24
|
-
|
|
25
|
-
describe(getDevToolsPluginClientAsync, () => {
|
|
26
|
-
const mockGetConnectionInfo = getConnectionInfo as jest.MockedFunction<typeof getConnectionInfo>;
|
|
27
|
-
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
jest.resetAllMocks();
|
|
30
|
-
cleanupDevToolsPluginInstances();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('should return a DevToolsPluginClientImplApp client when sender is from app', async () => {
|
|
34
|
-
mockGetConnectionInfo.mockReturnValue({
|
|
35
|
-
protocolVersion: TEST_PROTOCOL_VERSION,
|
|
36
|
-
sender: 'app',
|
|
37
|
-
devServer: 'localhost:8081',
|
|
38
|
-
});
|
|
39
|
-
const client = await getDevToolsPluginClientAsync('testPlugin');
|
|
40
|
-
expect(client).toBeInstanceOf(DevToolsPluginClientImplApp);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should return a DevToolsPluginClientImplApp client when sender is from browser', async () => {
|
|
44
|
-
mockGetConnectionInfo.mockReturnValue({
|
|
45
|
-
protocolVersion: TEST_PROTOCOL_VERSION,
|
|
46
|
-
sender: 'browser',
|
|
47
|
-
devServer: 'localhost:8081',
|
|
48
|
-
});
|
|
49
|
-
const client = await getDevToolsPluginClientAsync('testPlugin');
|
|
50
|
-
expect(client).toBeInstanceOf(DevToolsPluginClientImplBrowser);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should return the same client from the same plugin name when called multiple times', async () => {
|
|
54
|
-
mockGetConnectionInfo.mockReturnValue({
|
|
55
|
-
protocolVersion: TEST_PROTOCOL_VERSION,
|
|
56
|
-
sender: 'app',
|
|
57
|
-
devServer: 'localhost:8081',
|
|
58
|
-
});
|
|
59
|
-
const pluginName = 'testPlugin';
|
|
60
|
-
|
|
61
|
-
const client = await getDevToolsPluginClientAsync(pluginName);
|
|
62
|
-
const client2 = await getDevToolsPluginClientAsync(pluginName);
|
|
63
|
-
expect(client).toBe(client2);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should return a new client from the same plugin name from disconnected', async () => {
|
|
67
|
-
mockGetConnectionInfo.mockReturnValue({
|
|
68
|
-
protocolVersion: TEST_PROTOCOL_VERSION,
|
|
69
|
-
sender: 'app',
|
|
70
|
-
devServer: 'localhost:8081',
|
|
71
|
-
});
|
|
72
|
-
const pluginName = 'testPlugin';
|
|
73
|
-
jest.spyOn(DevToolsPluginClientImplApp.prototype, 'isConnected').mockReturnValue(false);
|
|
74
|
-
|
|
75
|
-
const client = await getDevToolsPluginClientAsync(pluginName);
|
|
76
|
-
const client2 = await getDevToolsPluginClientAsync(pluginName);
|
|
77
|
-
expect(client).not.toBe(client2);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('should return a new client from the same plugin name when devServer changed', async () => {
|
|
81
|
-
const pluginName = 'testPlugin';
|
|
82
|
-
|
|
83
|
-
mockGetConnectionInfo.mockReturnValueOnce({
|
|
84
|
-
protocolVersion: TEST_PROTOCOL_VERSION,
|
|
85
|
-
sender: 'app',
|
|
86
|
-
devServer: 'localhost:8081',
|
|
87
|
-
});
|
|
88
|
-
const client = await getDevToolsPluginClientAsync(pluginName);
|
|
89
|
-
|
|
90
|
-
mockGetConnectionInfo.mockReturnValueOnce({
|
|
91
|
-
protocolVersion: TEST_PROTOCOL_VERSION,
|
|
92
|
-
sender: 'app',
|
|
93
|
-
devServer: 'localhost:8082',
|
|
94
|
-
});
|
|
95
|
-
const client2 = await getDevToolsPluginClientAsync(pluginName);
|
|
96
|
-
|
|
97
|
-
expect(client).not.toBe(client2);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('should have at most one client per plugin name', async () => {
|
|
101
|
-
mockGetConnectionInfo.mockReturnValue({
|
|
102
|
-
protocolVersion: TEST_PROTOCOL_VERSION,
|
|
103
|
-
sender: 'app',
|
|
104
|
-
devServer: 'localhost:8081',
|
|
105
|
-
});
|
|
106
|
-
const pluginName = 'testPlugin';
|
|
107
|
-
const spy = jest.spyOn(DevToolsPluginClientImplApp.prototype, 'initAsync');
|
|
108
|
-
|
|
109
|
-
const [client1, client2, client3, anotherClient] = await Promise.all([
|
|
110
|
-
getDevToolsPluginClientAsync(pluginName),
|
|
111
|
-
getDevToolsPluginClientAsync(pluginName),
|
|
112
|
-
getDevToolsPluginClientAsync(pluginName),
|
|
113
|
-
getDevToolsPluginClientAsync('anotherPlugin'),
|
|
114
|
-
]);
|
|
115
|
-
expect(client1).toBe(client2);
|
|
116
|
-
expect(client1).toBe(client3);
|
|
117
|
-
expect(client1).not.toBe(anotherClient);
|
|
118
|
-
expect(spy).toHaveBeenCalledTimes(2);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { MessageFramePacker } from '../MessageFramePacker';
|
|
2
|
-
|
|
3
|
-
describe(MessageFramePacker, () => {
|
|
4
|
-
let packer: MessageFramePacker<string>;
|
|
5
|
-
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
packer = new MessageFramePacker();
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it('should pack and unpack a message with a string payload', async () => {
|
|
11
|
-
const messageFrame = {
|
|
12
|
-
messageKey: 'testKey',
|
|
13
|
-
payload: 'testPayload',
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const packedData = packer.pack(messageFrame);
|
|
17
|
-
if (packedData instanceof Promise || packedData instanceof Uint8Array) {
|
|
18
|
-
throw new Error('Unexpected packed data type');
|
|
19
|
-
}
|
|
20
|
-
const unpackedData = packer.unpack(packedData);
|
|
21
|
-
expect(unpackedData).toEqual(messageFrame);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
it('should pack and unpack a message with a number payload', async () => {
|
|
25
|
-
const messageFrame = {
|
|
26
|
-
messageKey: 'testKey',
|
|
27
|
-
payload: 12345,
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const packedData = packer.pack(messageFrame);
|
|
31
|
-
if (packedData instanceof Promise || packedData instanceof Uint8Array) {
|
|
32
|
-
throw new Error('Unexpected packed data type');
|
|
33
|
-
}
|
|
34
|
-
const unpackedData = packer.unpack(packedData);
|
|
35
|
-
expect(unpackedData).toEqual(messageFrame);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should pack and unpack a message with a null payload', async () => {
|
|
39
|
-
const messageFrame = {
|
|
40
|
-
messageKey: 'testKey',
|
|
41
|
-
payload: null,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const packedData = packer.pack(messageFrame);
|
|
45
|
-
if (packedData instanceof Promise || packedData instanceof Uint8Array) {
|
|
46
|
-
throw new Error('Unexpected packed data type');
|
|
47
|
-
}
|
|
48
|
-
const unpackedData = packer.unpack(packedData);
|
|
49
|
-
expect(unpackedData).toEqual(messageFrame);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should pack and unpack a message with an undefined payload', async () => {
|
|
53
|
-
const messageFrame = {
|
|
54
|
-
messageKey: 'testKey',
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const packedData = packer.pack(messageFrame);
|
|
58
|
-
if (packedData instanceof Promise || packedData instanceof Uint8Array) {
|
|
59
|
-
throw new Error('Unexpected packed data type');
|
|
60
|
-
}
|
|
61
|
-
const unpackedData = packer.unpack(packedData);
|
|
62
|
-
expect(unpackedData).toEqual(messageFrame);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('should pack and unpack a message with an object payload', async () => {
|
|
66
|
-
const messageFrame = {
|
|
67
|
-
messageKey: 'testKey',
|
|
68
|
-
payload: { key: 'value' },
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const packedData = packer.pack(messageFrame);
|
|
72
|
-
if (packedData instanceof Promise || packedData instanceof Uint8Array) {
|
|
73
|
-
throw new Error('Unexpected packed data type');
|
|
74
|
-
}
|
|
75
|
-
const unpackedData = packer.unpack(packedData);
|
|
76
|
-
expect(unpackedData).toEqual(messageFrame);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should pack and unpack a message with an Uint8Array payload', async () => {
|
|
80
|
-
const messageFrame = {
|
|
81
|
-
messageKey: 'testKey',
|
|
82
|
-
payload: new Uint8Array([1, 2, 3, 4]),
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const packedData = packer.pack(messageFrame);
|
|
86
|
-
if (packedData instanceof Promise || typeof packedData === 'string') {
|
|
87
|
-
throw new Error('Unexpected packed data type');
|
|
88
|
-
}
|
|
89
|
-
const unpackedData = packer.unpack(packedData.buffer as ArrayBuffer);
|
|
90
|
-
expect(unpackedData.messageKey).toEqual(messageFrame.messageKey);
|
|
91
|
-
expect(unpackedData.payload).toEqual(messageFrame.payload);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('should pack and unpack a message with an ArrayBuffer payload', async () => {
|
|
95
|
-
const buffer = new ArrayBuffer(4);
|
|
96
|
-
const view = new Uint8Array(buffer);
|
|
97
|
-
view.set([1, 2, 3, 4]);
|
|
98
|
-
|
|
99
|
-
const messageFrame = {
|
|
100
|
-
messageKey: 'testKey',
|
|
101
|
-
payload: buffer,
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const packedData = packer.pack(messageFrame);
|
|
105
|
-
if (packedData instanceof Promise || typeof packedData === 'string') {
|
|
106
|
-
throw new Error('Unexpected packed data type');
|
|
107
|
-
}
|
|
108
|
-
const unpackedData = packer.unpack(packedData.buffer as ArrayBuffer);
|
|
109
|
-
expect(unpackedData.messageKey).toEqual(messageFrame.messageKey);
|
|
110
|
-
expect(new Uint8Array(unpackedData.payload as ArrayBuffer)).toEqual(view);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it('should pack and unpack a message with a Blob payload', async () => {
|
|
114
|
-
const blob = new Blob(['testBlob'], { type: 'text/plain' });
|
|
115
|
-
|
|
116
|
-
const messageFrame = {
|
|
117
|
-
messageKey: 'testKey',
|
|
118
|
-
payload: blob,
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const packedDataPromise = packer.pack(messageFrame);
|
|
122
|
-
if (packedDataPromise instanceof Uint8Array || typeof packedDataPromise === 'string') {
|
|
123
|
-
throw new Error('Unexpected packed data type');
|
|
124
|
-
}
|
|
125
|
-
const packedData = await packedDataPromise;
|
|
126
|
-
const unpackedData = packer.unpack(packedData.buffer as ArrayBuffer);
|
|
127
|
-
expect(unpackedData.messageKey).toEqual(messageFrame.messageKey);
|
|
128
|
-
const unpackedBlob = unpackedData.payload as Blob;
|
|
129
|
-
expect(await unpackedBlob.text()).toEqual(await blob.text());
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('should pack and unpack a message with an object messageKey', async () => {
|
|
133
|
-
const packerWithObjectKey = new MessageFramePacker<{ key: string }>();
|
|
134
|
-
const messageFrame = {
|
|
135
|
-
messageKey: { key: 'value' },
|
|
136
|
-
payload: 'testPayload',
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
const packedData = packerWithObjectKey.pack(messageFrame);
|
|
140
|
-
if (packedData instanceof Promise || packedData instanceof Uint8Array) {
|
|
141
|
-
throw new Error('Unexpected packed data type');
|
|
142
|
-
}
|
|
143
|
-
const unpackedData = packerWithObjectKey.unpack(packedData);
|
|
144
|
-
expect(unpackedData).toEqual(messageFrame);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('should throw an error for an unsupported payload type', async () => {
|
|
148
|
-
const messageFrame = {
|
|
149
|
-
messageKey: 'testKey',
|
|
150
|
-
payload: () => {},
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
expect(() => {
|
|
154
|
-
packer.pack(messageFrame);
|
|
155
|
-
}).toThrow('Unsupported payload type');
|
|
156
|
-
});
|
|
157
|
-
});
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
interface EventSubscription {
|
|
2
|
-
listener: (payload: any) => void;
|
|
3
|
-
remove(): void;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
class EventEmitter {
|
|
7
|
-
listeners: Record<string, undefined | Set<(payload: any) => void>> = Object.create(null);
|
|
8
|
-
|
|
9
|
-
addListener(event: string, listener: (payload: any) => void) {
|
|
10
|
-
(this.listeners[event] || (this.listeners[event] = new Set())).add(listener);
|
|
11
|
-
return {
|
|
12
|
-
listener,
|
|
13
|
-
remove: () => this.listeners[event]?.delete(listener),
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
emit(event: string, payload?: any) {
|
|
18
|
-
const listeners = this.listeners[event];
|
|
19
|
-
if (listeners) {
|
|
20
|
-
for (const listener of listeners) listener(payload);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export default class MockWebSocket {
|
|
26
|
-
static readonly CONNECTING = 0;
|
|
27
|
-
static readonly OPEN = 1;
|
|
28
|
-
static readonly CLOSING = 2;
|
|
29
|
-
static readonly CLOSED = 3;
|
|
30
|
-
|
|
31
|
-
public readyState = MockWebSocket.CONNECTING;
|
|
32
|
-
|
|
33
|
-
private readonly emitter = new EventEmitter();
|
|
34
|
-
private subscriptions: EventSubscription[] = [];
|
|
35
|
-
private broadcastSubscriptions: EventSubscription[] = [];
|
|
36
|
-
|
|
37
|
-
// We use the broadcastEmitter to simulate event passing on a WebSocket server.
|
|
38
|
-
// The map is {address -> EventEmitter}
|
|
39
|
-
private static broadcastEmitter: Record<string, EventEmitter> = {};
|
|
40
|
-
|
|
41
|
-
constructor(private readonly address: string) {
|
|
42
|
-
MockWebSocket.broadcastEmitter[address] ??= new EventEmitter();
|
|
43
|
-
setTimeout(() => {
|
|
44
|
-
this.readyState = MockWebSocket.OPEN;
|
|
45
|
-
this.emitter.emit('open');
|
|
46
|
-
|
|
47
|
-
const subscription = MockWebSocket.broadcastEmitter[this.address].addListener(
|
|
48
|
-
'broadcast',
|
|
49
|
-
this.handleBroadcast
|
|
50
|
-
);
|
|
51
|
-
this.broadcastSubscriptions.push(subscription);
|
|
52
|
-
}, 0);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
addEventListener = jest.fn().mockImplementation((event, listener) => {
|
|
56
|
-
const subscription = this.emitter.addListener(event, listener);
|
|
57
|
-
this.subscriptions.push(subscription);
|
|
58
|
-
|
|
59
|
-
const broadcastSubscription = MockWebSocket.broadcastEmitter[this.address].addListener(
|
|
60
|
-
event,
|
|
61
|
-
listener
|
|
62
|
-
);
|
|
63
|
-
this.broadcastSubscriptions.push(broadcastSubscription);
|
|
64
|
-
});
|
|
65
|
-
removeEventListener = jest.fn().mockImplementation((event, listener) => {
|
|
66
|
-
const index = this.subscriptions.findIndex(
|
|
67
|
-
(subscription) => subscription.listener === listener
|
|
68
|
-
);
|
|
69
|
-
if (index >= 0) {
|
|
70
|
-
this.subscriptions[index].remove();
|
|
71
|
-
this.subscriptions.splice(index, 1);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const broadcastIndex = this.broadcastSubscriptions.findIndex(
|
|
75
|
-
(subscription) => subscription.listener === listener
|
|
76
|
-
);
|
|
77
|
-
if (broadcastIndex >= 0) {
|
|
78
|
-
this.broadcastSubscriptions[broadcastIndex].remove();
|
|
79
|
-
this.broadcastSubscriptions.splice(broadcastIndex, 1);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
close = jest.fn().mockImplementation(() => {
|
|
84
|
-
for (const subscription of this.broadcastSubscriptions) {
|
|
85
|
-
subscription.remove();
|
|
86
|
-
}
|
|
87
|
-
this.broadcastSubscriptions = [];
|
|
88
|
-
this.readyState = MockWebSocket.CLOSED;
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
send = jest.fn().mockImplementation((data) => {
|
|
92
|
-
MockWebSocket.broadcastEmitter[this.address].emit('broadcast', { sender: this, data });
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
private handleBroadcast = ({ sender, data }: any) => {
|
|
96
|
-
if (sender !== this) {
|
|
97
|
-
this.emitter.emit('message', { data });
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
}
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { WebSocket, WebSocketServer } from 'ws';
|
|
2
|
-
|
|
3
|
-
import { WebSocketWithReconnect } from '../WebSocketWithReconnect';
|
|
4
|
-
|
|
5
|
-
// @ts-expect-error - The WebSocket from ws is not compatible with the globalThis.WebSocket
|
|
6
|
-
globalThis.WebSocket = WebSocket;
|
|
7
|
-
|
|
8
|
-
describe(WebSocketWithReconnect, () => {
|
|
9
|
-
let ws: WebSocketWithReconnect | null = null;
|
|
10
|
-
let server: WebSocketServer | null = null;
|
|
11
|
-
|
|
12
|
-
afterEach(async () => {
|
|
13
|
-
ws?.close();
|
|
14
|
-
await closeServerAsync(server);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('should act as a WebSocket', async () => {
|
|
18
|
-
server = new WebSocketServer({ port: 8000 });
|
|
19
|
-
// An echoing server
|
|
20
|
-
server?.addListener('connection', (socket) => {
|
|
21
|
-
socket.addEventListener('message', (e) => {
|
|
22
|
-
socket.send(e.data);
|
|
23
|
-
});
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
ws = new WebSocketWithReconnect('ws://localhost:8000');
|
|
27
|
-
let received = '';
|
|
28
|
-
ws.addEventListener('message', (e) => {
|
|
29
|
-
received = e.data;
|
|
30
|
-
});
|
|
31
|
-
await new Promise((resolve) => {
|
|
32
|
-
ws?.addEventListener('open', () => {
|
|
33
|
-
resolve(null);
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
expect(ws.readyState).toBe(WebSocket.OPEN);
|
|
37
|
-
|
|
38
|
-
ws.send('hello');
|
|
39
|
-
await delayAsync(50);
|
|
40
|
-
expect(received).toBe('hello');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('should reconnect when the connection is lost', async () => {
|
|
44
|
-
server = new WebSocketServer({ port: 8000 });
|
|
45
|
-
|
|
46
|
-
const mockOnReconnect = jest.fn();
|
|
47
|
-
ws = new WebSocketWithReconnect('ws://localhost:8000', {
|
|
48
|
-
retriesInterval: 10,
|
|
49
|
-
onReconnect: mockOnReconnect,
|
|
50
|
-
});
|
|
51
|
-
await new Promise((resolve) => {
|
|
52
|
-
ws?.addEventListener('open', () => {
|
|
53
|
-
resolve(null);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
expect(ws.readyState).toBe(WebSocket.OPEN);
|
|
57
|
-
|
|
58
|
-
await closeServerAsync(server);
|
|
59
|
-
server = new WebSocketServer({ port: 8000 });
|
|
60
|
-
await delayAsync(100);
|
|
61
|
-
|
|
62
|
-
expect(ws.readyState).toBe(WebSocket.OPEN);
|
|
63
|
-
expect(mockOnReconnect).toHaveBeenCalled();
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should keep sending messages when retrying connection', async () => {
|
|
67
|
-
server = new WebSocketServer({ port: 8000 });
|
|
68
|
-
ws = new WebSocketWithReconnect('ws://localhost:8000', {
|
|
69
|
-
retriesInterval: 10,
|
|
70
|
-
});
|
|
71
|
-
await new Promise((resolve) => {
|
|
72
|
-
ws?.addEventListener('open', () => {
|
|
73
|
-
resolve(null);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
expect(ws.readyState).toBe(WebSocket.OPEN);
|
|
77
|
-
|
|
78
|
-
await closeServerAsync(server);
|
|
79
|
-
|
|
80
|
-
expect(ws.readyState).not.toBe(WebSocket.OPEN);
|
|
81
|
-
ws.send('hello');
|
|
82
|
-
|
|
83
|
-
server = new WebSocketServer({ port: 8000 });
|
|
84
|
-
const receivedPromise = new Promise((resolve) => {
|
|
85
|
-
server?.addListener('connection', (socket) => {
|
|
86
|
-
socket.addEventListener('message', (e) => {
|
|
87
|
-
resolve(e.data);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
await delayAsync(30);
|
|
93
|
-
expect(ws.readyState).toBe(WebSocket.OPEN);
|
|
94
|
-
const received = await receivedPromise;
|
|
95
|
-
expect(received).toBe('hello');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('should throw errors if exceeds max retries', async () => {
|
|
99
|
-
server = new WebSocketServer({ port: 8000 });
|
|
100
|
-
|
|
101
|
-
const mockOnError = jest.fn();
|
|
102
|
-
ws = new WebSocketWithReconnect('ws://localhost:8000', {
|
|
103
|
-
retriesInterval: 10,
|
|
104
|
-
maxRetries: 2,
|
|
105
|
-
onError: mockOnError,
|
|
106
|
-
});
|
|
107
|
-
await closeServerAsync(server);
|
|
108
|
-
await delayAsync(100);
|
|
109
|
-
expect(mockOnError).toHaveBeenCalled();
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('should show connecting state when reconnecting', async () => {
|
|
113
|
-
server = new WebSocketServer({ port: 8000 });
|
|
114
|
-
|
|
115
|
-
ws = new WebSocketWithReconnect('ws://localhost:8000', {
|
|
116
|
-
retriesInterval: 1000,
|
|
117
|
-
});
|
|
118
|
-
await closeServerAsync(server);
|
|
119
|
-
await delayAsync(100);
|
|
120
|
-
expect(ws.readyState).toBe(WebSocket.CONNECTING);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('should not emit close event when reconnecting until exceeds maxRetries', async () => {
|
|
124
|
-
server = new WebSocketServer({ port: 8000 });
|
|
125
|
-
|
|
126
|
-
const mockOnError = jest.fn();
|
|
127
|
-
ws = new WebSocketWithReconnect('ws://localhost:8000', {
|
|
128
|
-
retriesInterval: 50,
|
|
129
|
-
maxRetries: 2,
|
|
130
|
-
onError: mockOnError,
|
|
131
|
-
});
|
|
132
|
-
const mockClose = jest.fn();
|
|
133
|
-
ws.addEventListener('close', mockClose);
|
|
134
|
-
await closeServerAsync(server);
|
|
135
|
-
await delayAsync(50);
|
|
136
|
-
expect(mockClose).not.toHaveBeenCalled();
|
|
137
|
-
await delayAsync(100);
|
|
138
|
-
expect(mockClose).toHaveBeenCalled();
|
|
139
|
-
expect(ws.readyState).toBe(WebSocket.CLOSED);
|
|
140
|
-
expect(mockOnError).toHaveBeenCalled();
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('should support arraybuffer binaryType option', async () => {
|
|
144
|
-
server = new WebSocketServer({ port: 8000 });
|
|
145
|
-
// An echoing server
|
|
146
|
-
server?.addListener('connection', (socket) => {
|
|
147
|
-
socket.addEventListener('message', (e) => {
|
|
148
|
-
socket.send(e.data);
|
|
149
|
-
});
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
ws = new WebSocketWithReconnect('ws://localhost:8000', { binaryType: 'arraybuffer' });
|
|
153
|
-
let received: any = null;
|
|
154
|
-
ws.addEventListener('message', (e) => {
|
|
155
|
-
received = e.data;
|
|
156
|
-
});
|
|
157
|
-
await new Promise((resolve) => {
|
|
158
|
-
ws?.addEventListener('open', () => {
|
|
159
|
-
resolve(null);
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
expect(ws.readyState).toBe(WebSocket.OPEN);
|
|
163
|
-
|
|
164
|
-
ws.send(new Uint8Array([0x01, 0x02, 0x03]));
|
|
165
|
-
await delayAsync(50);
|
|
166
|
-
expect(received).toBeInstanceOf(ArrayBuffer);
|
|
167
|
-
expect(new Uint8Array(received as ArrayBuffer)).toEqual(new Uint8Array([0x01, 0x02, 0x03]));
|
|
168
|
-
});
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
async function closeServerAsync(server: WebSocketServer | null) {
|
|
172
|
-
if (server == null) {
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
server.close();
|
|
176
|
-
for (const client of server.clients) {
|
|
177
|
-
client.terminate();
|
|
178
|
-
}
|
|
179
|
-
await delayAsync(30);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async function delayAsync(timeMs: number) {
|
|
183
|
-
return new Promise((resolve) => setTimeout(resolve, timeMs));
|
|
184
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import * as logger from '../logger';
|
|
2
|
-
|
|
3
|
-
describe('logger', () => {
|
|
4
|
-
const spyLog = jest.spyOn(console, 'info').mockImplementation(() => {});
|
|
5
|
-
|
|
6
|
-
afterEach(() => {
|
|
7
|
-
logger.setEnableLogging(false);
|
|
8
|
-
});
|
|
9
|
-
it('should not log by default', () => {
|
|
10
|
-
logger.info('test');
|
|
11
|
-
expect(spyLog).not.toHaveBeenCalled();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('should logger when enabled', () => {
|
|
15
|
-
logger.setEnableLogging(true);
|
|
16
|
-
logger.info('test logging');
|
|
17
|
-
expect(spyLog).toHaveBeenCalled();
|
|
18
|
-
expect(spyLog.mock.calls[0][0]).toBe('test logging');
|
|
19
|
-
});
|
|
20
|
-
});
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import type { WebSocketBackingStore } from './WebSocketBackingStore';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The connection info for devtools plugins client.
|
|
5
|
-
*/
|
|
6
|
-
export interface ConnectionInfo {
|
|
7
|
-
/** Indicates the sender towards the devtools plugin. */
|
|
8
|
-
sender:
|
|
9
|
-
| 'app' // client running in the app environment.
|
|
10
|
-
| 'browser'; // client running in the browser environment.
|
|
11
|
-
|
|
12
|
-
/** Dev server address. */
|
|
13
|
-
devServer: string;
|
|
14
|
-
|
|
15
|
-
/** The plugin name. */
|
|
16
|
-
pluginName: string;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* The backing store for the WebSocket connection. Exposed for testing.
|
|
20
|
-
* If not provided, the default singleton instance will be used.
|
|
21
|
-
* @hidden
|
|
22
|
-
*/
|
|
23
|
-
wsStore?: WebSocketBackingStore;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* The transport protocol version between the app and the webui.
|
|
27
|
-
*/
|
|
28
|
-
protocolVersion: number;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Options for the devtools plugin client.
|
|
33
|
-
*/
|
|
34
|
-
export interface DevToolsPluginClientOptions {
|
|
35
|
-
/**
|
|
36
|
-
* The underlying WebSocket [`binaryType`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/binaryType).
|
|
37
|
-
*/
|
|
38
|
-
websocketBinaryType?: 'arraybuffer' | 'blob';
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* The handshake messages for the devtools plugin client.
|
|
43
|
-
* @hidden
|
|
44
|
-
*/
|
|
45
|
-
export interface HandshakeMessageParams {
|
|
46
|
-
protocolVersion: number;
|
|
47
|
-
pluginName: string;
|
|
48
|
-
method: 'handshake' | 'terminateBrowserClient';
|
|
49
|
-
browserClientId: string;
|
|
50
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Get the dev server address.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { PROTOCOL_VERSION } from './ProtocolVersion';
|
|
6
|
-
import type { ConnectionInfo } from './devtools.types';
|
|
7
|
-
|
|
8
|
-
export function getConnectionInfo(): Omit<ConnectionInfo, 'pluginName'> {
|
|
9
|
-
const getDevServer = require('react-native/Libraries/Core/Devtools/getDevServer').default;
|
|
10
|
-
const devServer = getDevServer()
|
|
11
|
-
.url.replace(/^https?:\/\//, '')
|
|
12
|
-
.replace(/\/?$/, '') as string;
|
|
13
|
-
return {
|
|
14
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
15
|
-
sender: 'app',
|
|
16
|
-
devServer,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Get the dev server address.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { PROTOCOL_VERSION } from './ProtocolVersion';
|
|
6
|
-
import type { ConnectionInfo } from './devtools.types';
|
|
7
|
-
|
|
8
|
-
export function getConnectionInfo(): Omit<ConnectionInfo, 'pluginName'> {
|
|
9
|
-
const devServerQuery = new URLSearchParams(window.location.search).get('devServer');
|
|
10
|
-
const host = window.location.origin.replace(/^https?:\/\//, '');
|
|
11
|
-
return {
|
|
12
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
13
|
-
sender: 'browser',
|
|
14
|
-
devServer: devServerQuery || host,
|
|
15
|
-
};
|
|
16
|
-
}
|