expo 54.0.0-canary-20250722-599a28f → 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.
Files changed (89) hide show
  1. package/android/build.gradle +2 -2
  2. package/android/src/main/java/expo/modules/ExpoReactHostFactory.kt +1 -7
  3. package/android/src/main/java/expo/modules/ReactActivityDelegateWrapper.kt +8 -8
  4. package/android/src/main/java/expo/modules/ReactNativeHostWrapper.kt +0 -5
  5. package/android/src/test/resources/robolectric.properties +1 -0
  6. package/build/Expo.d.ts +9 -1
  7. package/build/Expo.d.ts.map +1 -1
  8. package/build/async-require/fetchAsync.native.d.ts.map +1 -1
  9. package/build/async-require/hmr.d.ts.map +1 -1
  10. package/build/winter/FormData.d.ts.map +1 -1
  11. package/build/winter/fetch/RequestUtils.d.ts.map +1 -1
  12. package/build/winter/fetch/convertFormData.d.ts.map +1 -1
  13. package/build/winter/installGlobal.d.ts +25 -0
  14. package/build/winter/installGlobal.d.ts.map +1 -0
  15. package/bundledNativeModules.json +94 -92
  16. package/devtools.d.ts +1 -1
  17. package/devtools.js +1 -1
  18. package/internal/babel-preset.d.ts +2 -0
  19. package/internal/babel-preset.js +2 -0
  20. package/internal/unstable-autolinking-exports.d.ts +2 -0
  21. package/internal/unstable-autolinking-exports.js +2 -0
  22. package/internal/unstable-expo-updates-cli-exports.d.ts +2 -0
  23. package/internal/unstable-expo-updates-cli-exports.js +2 -0
  24. package/ios/AppDelegates/ExpoAppDelegate.swift +1 -1
  25. package/ios/AppDelegates/ExpoReactNativeFactory.swift +2 -2
  26. package/ios/AppDelegates/ExpoReactNativeFactoryDelegate.swift +1 -1
  27. package/ios/AppDelegates/RCTAppDelegateUmbrella.h +5 -0
  28. package/ios/Fetch/ExpoFetchCustomExtension.swift +1 -1
  29. package/ios/Fetch/ExpoFetchModule.swift +3 -3
  30. package/ios/Fetch/ExpoURLSessionTask.swift +2 -2
  31. package/ios/Fetch/NativeRequest.swift +1 -1
  32. package/ios/Fetch/NativeResponse.swift +2 -2
  33. package/package.json +27 -24
  34. package/src/Expo.ts +4 -0
  35. package/src/async-require/fetchAsync.native.ts +48 -10
  36. package/src/async-require/hmr.ts +1 -2
  37. package/src/async-require/messageSocket.native.ts +0 -3
  38. package/src/winter/FormData.ts +1 -9
  39. package/src/winter/fetch/RequestUtils.ts +15 -1
  40. package/src/winter/fetch/__tests__/convertFormData-test.native.ts +1 -3
  41. package/src/winter/fetch/convertFormData.ts +56 -27
  42. package/src/winter/installGlobal.ts +109 -0
  43. package/src/winter/runtime.native.ts +1 -20
  44. package/template.tgz +0 -0
  45. package/build/devtools/DevToolsPluginClient.d.ts +0 -72
  46. package/build/devtools/DevToolsPluginClient.d.ts.map +0 -1
  47. package/build/devtools/DevToolsPluginClientFactory.d.ts +0 -16
  48. package/build/devtools/DevToolsPluginClientFactory.d.ts.map +0 -1
  49. package/build/devtools/DevToolsPluginClientImplApp.d.ts +0 -15
  50. package/build/devtools/DevToolsPluginClientImplApp.d.ts.map +0 -1
  51. package/build/devtools/DevToolsPluginClientImplBrowser.d.ts +0 -14
  52. package/build/devtools/DevToolsPluginClientImplBrowser.d.ts.map +0 -1
  53. package/build/devtools/MessageFramePacker.d.ts +0 -50
  54. package/build/devtools/MessageFramePacker.d.ts.map +0 -1
  55. package/build/devtools/ProtocolVersion.d.ts +0 -7
  56. package/build/devtools/ProtocolVersion.d.ts.map +0 -1
  57. package/build/devtools/WebSocketBackingStore.d.ts +0 -10
  58. package/build/devtools/WebSocketBackingStore.d.ts.map +0 -1
  59. package/build/devtools/WebSocketWithReconnect.d.ts +0 -81
  60. package/build/devtools/WebSocketWithReconnect.d.ts.map +0 -1
  61. package/build/devtools/devtools.types.d.ts +0 -42
  62. package/build/devtools/devtools.types.d.ts.map +0 -1
  63. package/build/devtools/getConnectionInfo.d.ts +0 -6
  64. package/build/devtools/getConnectionInfo.d.ts.map +0 -1
  65. package/build/devtools/getConnectionInfo.native.d.ts +0 -6
  66. package/build/devtools/getConnectionInfo.native.d.ts.map +0 -1
  67. package/build/devtools/index.d.ts +0 -12
  68. package/build/devtools/index.d.ts.map +0 -1
  69. package/build/devtools/logger.d.ts +0 -6
  70. package/build/devtools/logger.d.ts.map +0 -1
  71. package/src/devtools/DevToolsPluginClient.ts +0 -240
  72. package/src/devtools/DevToolsPluginClientFactory.ts +0 -73
  73. package/src/devtools/DevToolsPluginClientImplApp.ts +0 -56
  74. package/src/devtools/DevToolsPluginClientImplBrowser.ts +0 -38
  75. package/src/devtools/MessageFramePacker.ts +0 -235
  76. package/src/devtools/ProtocolVersion.ts +0 -6
  77. package/src/devtools/WebSocketBackingStore.ts +0 -10
  78. package/src/devtools/WebSocketWithReconnect.ts +0 -318
  79. package/src/devtools/__tests__/DevToolsPluginClient-test.ts +0 -285
  80. package/src/devtools/__tests__/DevToolsPluginClientFactory-test.ts +0 -120
  81. package/src/devtools/__tests__/MessageFramePack-test.node.ts +0 -157
  82. package/src/devtools/__tests__/MockWebSocket.ts +0 -100
  83. package/src/devtools/__tests__/WebSocketWithReconnect-test.node.ts +0 -184
  84. package/src/devtools/__tests__/logger-test.ts +0 -20
  85. package/src/devtools/devtools.types.ts +0 -50
  86. package/src/devtools/getConnectionInfo.native.ts +0 -18
  87. package/src/devtools/getConnectionInfo.ts +0 -16
  88. package/src/devtools/index.ts +0 -53
  89. package/src/devtools/logger.ts +0 -29
@@ -1,285 +0,0 @@
1
- /// <reference types="node" />
2
-
3
- import { TextDecoder, TextEncoder } from 'node:util';
4
-
5
- import MockWebSocket from './MockWebSocket';
6
- import { DevToolsPluginClient } from '../DevToolsPluginClient';
7
- import { createDevToolsPluginClient } from '../DevToolsPluginClientFactory';
8
- import { WebSocketBackingStore } from '../WebSocketBackingStore';
9
-
10
- // @ts-expect-error - We don't mock all properties from WebSocket
11
- globalThis.WebSocket = MockWebSocket;
12
-
13
- // @ts-ignore - TextDecoder and TextEncoder are not defined in native jest environments.
14
- globalThis.TextDecoder ??= TextDecoder;
15
- globalThis.TextEncoder ??= TextEncoder;
16
-
17
- const TEST_PROTOCOL_VERSION = 1;
18
-
19
- describe(`DevToolsPluginClient`, () => {
20
- let appClient: DevToolsPluginClient;
21
- let testCaseCounter = 0;
22
- let devServer: string;
23
- const pluginName = 'testPlugin';
24
-
25
- beforeEach(async () => {
26
- // Connect to different devServer for each test case to avoid jest parallel test issues.
27
- testCaseCounter += 1;
28
- devServer = `localhost:${8000 + testCaseCounter}`;
29
- appClient = await createDevToolsPluginClient({
30
- protocolVersion: TEST_PROTOCOL_VERSION,
31
- devServer,
32
- sender: 'app',
33
- pluginName,
34
- wsStore: new WebSocketBackingStore(),
35
- });
36
- });
37
-
38
- afterEach(async () => {
39
- await appClient.closeAsync();
40
- });
41
-
42
- it('should connect to the WebSocket server', async () => {
43
- expect(appClient.isConnected()).toBe(true);
44
- });
45
- });
46
-
47
- describe(`DevToolsPluginClient (browser <> app)`, () => {
48
- let testCaseCounter = 0;
49
- let devServer: string;
50
- const pluginName = 'testPlugin';
51
- let appClient: DevToolsPluginClient;
52
- let browserClient: DevToolsPluginClient;
53
-
54
- beforeEach(() => {
55
- // Connect to different devServer for each test case to avoid jest parallel test issues.
56
- testCaseCounter += 1;
57
- devServer = `localhost:${8000 + testCaseCounter}`;
58
- });
59
-
60
- afterEach(async () => {
61
- await appClient?.closeAsync();
62
- await browserClient?.closeAsync();
63
- });
64
-
65
- it('should send and receive messages', async () => {
66
- const method = 'testMethod';
67
- const message = { foo: 'bar' };
68
-
69
- appClient = await createDevToolsPluginClient({
70
- protocolVersion: TEST_PROTOCOL_VERSION,
71
- devServer,
72
- sender: 'app',
73
- pluginName,
74
- wsStore: new WebSocketBackingStore(),
75
- });
76
- browserClient = await createDevToolsPluginClient({
77
- protocolVersion: TEST_PROTOCOL_VERSION,
78
- devServer,
79
- sender: 'browser',
80
- pluginName,
81
- wsStore: new WebSocketBackingStore(),
82
- });
83
-
84
- const receivedPromise = new Promise((resolve) => {
85
- appClient.addMessageListener(method, (params) => {
86
- resolve(params);
87
- });
88
- });
89
-
90
- browserClient.sendMessage(method, message);
91
- const received = await receivedPromise;
92
- expect(received).toEqual(message);
93
- });
94
-
95
- it('should support ping-pong messages', async () => {
96
- appClient = await createDevToolsPluginClient({
97
- protocolVersion: TEST_PROTOCOL_VERSION,
98
- devServer,
99
- sender: 'app',
100
- pluginName,
101
- wsStore: new WebSocketBackingStore(),
102
- });
103
- browserClient = await createDevToolsPluginClient({
104
- protocolVersion: TEST_PROTOCOL_VERSION,
105
- devServer,
106
- sender: 'browser',
107
- pluginName,
108
- wsStore: new WebSocketBackingStore(),
109
- });
110
-
111
- const appPromise = new Promise((resolve) => {
112
- appClient.addMessageListener('ping', (params) => {
113
- appClient.sendMessage('pong', { from: 'app' });
114
- resolve(params);
115
- });
116
- });
117
- const browserPromise = new Promise((resolve) => {
118
- browserClient.addMessageListener('pong', (params) => {
119
- resolve(params);
120
- });
121
- });
122
-
123
- browserClient.sendMessage('ping', { from: 'browser' });
124
- const receivedPing = await appPromise;
125
- expect(receivedPing).toEqual({ from: 'browser' });
126
- const receivedPong = await browserPromise;
127
- expect(receivedPong).toEqual({ from: 'app' });
128
- });
129
-
130
- it('should not receive messages from differnet plugin', async () => {
131
- const method = 'testMethod';
132
- const message = { foo: 'bar' };
133
-
134
- appClient = await createDevToolsPluginClient({
135
- protocolVersion: TEST_PROTOCOL_VERSION,
136
- devServer,
137
- sender: 'app',
138
- pluginName,
139
- wsStore: new WebSocketBackingStore(),
140
- });
141
- browserClient = await createDevToolsPluginClient({
142
- protocolVersion: TEST_PROTOCOL_VERSION,
143
- devServer,
144
- sender: 'browser',
145
- pluginName: 'pluginB',
146
- wsStore: new WebSocketBackingStore(),
147
- });
148
-
149
- const receivedPromise = new Promise((resolve) => {
150
- appClient.addMessageListener(method, (params) => {
151
- resolve(params);
152
- });
153
- });
154
-
155
- browserClient.sendMessage(method, message);
156
- expect(receivedPromise).rejects.toThrow();
157
- });
158
-
159
- it('should only allow the latest connected client with the same plugin name to receive messages', async () => {
160
- const method = 'testMethod';
161
-
162
- appClient = await createDevToolsPluginClient({
163
- protocolVersion: TEST_PROTOCOL_VERSION,
164
- devServer,
165
- sender: 'app',
166
- pluginName,
167
- wsStore: new WebSocketBackingStore(),
168
- });
169
- const receivedMessages: any[] = [];
170
- appClient.addMessageListener(method, (params) => {
171
- receivedMessages.push(params);
172
- });
173
-
174
- browserClient = await createDevToolsPluginClient({
175
- protocolVersion: TEST_PROTOCOL_VERSION,
176
- devServer,
177
- sender: 'browser',
178
- pluginName,
179
- wsStore: new WebSocketBackingStore(),
180
- });
181
-
182
- await delayAsync(100);
183
- const browserClient2 = await createDevToolsPluginClient({
184
- protocolVersion: TEST_PROTOCOL_VERSION,
185
- devServer,
186
- sender: 'browser',
187
- pluginName,
188
- wsStore: new WebSocketBackingStore(),
189
- });
190
-
191
- await delayAsync(100);
192
- expect(browserClient.isConnected()).toBe(false);
193
- expect(browserClient2.isConnected()).toBe(true);
194
- browserClient.sendMessage(method, { from: 'browserClient' });
195
- browserClient2.sendMessage(method, { from: 'browserClient2' });
196
-
197
- await delayAsync(100);
198
- expect(receivedMessages.length).toBe(1);
199
- expect(receivedMessages[0]).toEqual({ from: 'browserClient2' });
200
- await browserClient2.closeAsync();
201
- });
202
-
203
- it('should terminate client from incompatible protocol version', async () => {
204
- const spyConsoleInfo = jest.spyOn(console, 'info').mockImplementation(() => {});
205
- const spyConsoleWarn = jest.spyOn(console, 'warn').mockImplementation(() => {});
206
- appClient = await createDevToolsPluginClient({
207
- protocolVersion: TEST_PROTOCOL_VERSION,
208
- devServer,
209
- sender: 'app',
210
- pluginName,
211
- wsStore: new WebSocketBackingStore(),
212
- });
213
- browserClient = await createDevToolsPluginClient({
214
- protocolVersion: -1,
215
- devServer,
216
- sender: 'browser',
217
- pluginName,
218
- wsStore: new WebSocketBackingStore(),
219
- });
220
-
221
- await delayAsync(100);
222
- expect(spyConsoleWarn).toHaveBeenCalledWith(
223
- `Received an incompatible devtools plugin handshake message - pluginName[${pluginName}]`
224
- );
225
- expect(browserClient.isConnected()).toBe(false);
226
-
227
- spyConsoleInfo.mockRestore();
228
- spyConsoleWarn.mockRestore();
229
- });
230
- });
231
-
232
- describe(`DevToolsPluginClient - multiplexing`, () => {
233
- let testCaseCounter = 0;
234
- let devServer: string;
235
-
236
- beforeEach(() => {
237
- // Connect to different devServer for each test case to avoid jest parallel test issues.
238
- testCaseCounter += 1;
239
- devServer = `localhost:${8000 + testCaseCounter}`;
240
- });
241
-
242
- it('should not close the websocket connection while there are alive clients', async () => {
243
- const wsStore = new WebSocketBackingStore();
244
- const appClient1 = await createDevToolsPluginClient({
245
- protocolVersion: TEST_PROTOCOL_VERSION,
246
- devServer,
247
- sender: 'app',
248
- pluginName: 'plugin1',
249
- wsStore,
250
- });
251
- const appClient2 = await createDevToolsPluginClient({
252
- protocolVersion: TEST_PROTOCOL_VERSION,
253
- devServer,
254
- sender: 'app',
255
- pluginName: 'plugin2',
256
- wsStore,
257
- });
258
- expect(appClient1.isConnected()).toBe(true);
259
- expect(appClient2.isConnected()).toBe(true);
260
- expect(appClient1.getWebSocketBackingStore()).toBe(wsStore);
261
- expect(appClient2.getWebSocketBackingStore()).toBe(wsStore);
262
- expect(wsStore.refCount).toBe(2);
263
- const ws = wsStore.ws;
264
- if (!ws) {
265
- throw new Error('Null WebSocket');
266
- }
267
- const mockClose = jest.spyOn(ws, 'close');
268
- expect(mockClose).toHaveBeenCalledTimes(0);
269
- expect(ws.readyState).toBe(WebSocket.OPEN);
270
-
271
- await appClient1.closeAsync();
272
- expect(mockClose).toHaveBeenCalledTimes(0);
273
- expect(wsStore.refCount).toBe(1);
274
- expect(ws.readyState).toBe(WebSocket.OPEN);
275
-
276
- await appClient2.closeAsync();
277
- expect(mockClose).toHaveBeenCalledTimes(1);
278
- expect(wsStore.refCount).toBe(0);
279
- expect(ws.readyState).toBe(WebSocket.CLOSED);
280
- });
281
- });
282
-
283
- async function delayAsync(timeMs: number): Promise<void> {
284
- return new Promise((resolve) => setTimeout(resolve, timeMs));
285
- }
@@ -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
- }