expo 54.0.0-canary-20250722-599a28f → 54.0.0-canary-20250826-f475166

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 (99) 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 +33 -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/FetchResponse.d.ts +2 -2
  12. package/build/winter/fetch/FetchResponse.d.ts.map +1 -1
  13. package/build/winter/fetch/NativeRequest.d.ts +1 -1
  14. package/build/winter/fetch/NativeRequest.d.ts.map +1 -1
  15. package/build/winter/fetch/RequestUtils.d.ts.map +1 -1
  16. package/build/winter/fetch/convertFormData.d.ts.map +1 -1
  17. package/build/winter/installGlobal.d.ts +25 -0
  18. package/build/winter/installGlobal.d.ts.map +1 -0
  19. package/bundledNativeModules.json +97 -96
  20. package/devtools.d.ts +1 -1
  21. package/devtools.js +1 -1
  22. package/internal/babel-preset.d.ts +2 -0
  23. package/internal/babel-preset.js +2 -0
  24. package/internal/install-global.d.ts +2 -0
  25. package/internal/install-global.js +5 -0
  26. package/internal/unstable-autolinking-exports.d.ts +2 -0
  27. package/internal/unstable-autolinking-exports.js +2 -0
  28. package/internal/unstable-expo-updates-cli-exports.d.ts +2 -0
  29. package/internal/unstable-expo-updates-cli-exports.js +2 -0
  30. package/ios/AppDelegates/ExpoAppDelegate.swift +1 -1
  31. package/ios/AppDelegates/ExpoReactNativeFactory.swift +15 -2
  32. package/ios/AppDelegates/ExpoReactNativeFactoryDelegate.swift +1 -1
  33. package/ios/AppDelegates/RCTAppDelegateUmbrella.h +5 -0
  34. package/ios/Fetch/ExpoFetchCustomExtension.swift +1 -1
  35. package/ios/Fetch/ExpoFetchModule.swift +3 -3
  36. package/ios/Fetch/ExpoURLSessionTask.swift +2 -2
  37. package/ios/Fetch/NativeRequest.swift +1 -1
  38. package/ios/Fetch/NativeResponse.swift +2 -2
  39. package/package.json +29 -27
  40. package/src/Expo.ts +4 -0
  41. package/src/async-require/fetchAsync.native.ts +48 -10
  42. package/src/async-require/hmr.ts +1 -2
  43. package/src/async-require/messageSocket.native.ts +0 -3
  44. package/src/winter/FormData.ts +1 -9
  45. package/src/winter/__tests__/structuredClone.test.ios.ts +27 -0
  46. package/src/winter/fetch/FetchResponse.ts +4 -4
  47. package/src/winter/fetch/NativeRequest.ts +1 -1
  48. package/src/winter/fetch/RequestUtils.ts +15 -1
  49. package/src/winter/fetch/__tests__/RequestUtils-test.ts +7 -7
  50. package/src/winter/fetch/__tests__/convertFormData-test.native.ts +1 -3
  51. package/src/winter/fetch/convertFormData.ts +56 -27
  52. package/src/winter/installGlobal.ts +109 -0
  53. package/src/winter/runtime.native.ts +4 -20
  54. package/template.tgz +0 -0
  55. package/build/devtools/DevToolsPluginClient.d.ts +0 -72
  56. package/build/devtools/DevToolsPluginClient.d.ts.map +0 -1
  57. package/build/devtools/DevToolsPluginClientFactory.d.ts +0 -16
  58. package/build/devtools/DevToolsPluginClientFactory.d.ts.map +0 -1
  59. package/build/devtools/DevToolsPluginClientImplApp.d.ts +0 -15
  60. package/build/devtools/DevToolsPluginClientImplApp.d.ts.map +0 -1
  61. package/build/devtools/DevToolsPluginClientImplBrowser.d.ts +0 -14
  62. package/build/devtools/DevToolsPluginClientImplBrowser.d.ts.map +0 -1
  63. package/build/devtools/MessageFramePacker.d.ts +0 -50
  64. package/build/devtools/MessageFramePacker.d.ts.map +0 -1
  65. package/build/devtools/ProtocolVersion.d.ts +0 -7
  66. package/build/devtools/ProtocolVersion.d.ts.map +0 -1
  67. package/build/devtools/WebSocketBackingStore.d.ts +0 -10
  68. package/build/devtools/WebSocketBackingStore.d.ts.map +0 -1
  69. package/build/devtools/WebSocketWithReconnect.d.ts +0 -81
  70. package/build/devtools/WebSocketWithReconnect.d.ts.map +0 -1
  71. package/build/devtools/devtools.types.d.ts +0 -42
  72. package/build/devtools/devtools.types.d.ts.map +0 -1
  73. package/build/devtools/getConnectionInfo.d.ts +0 -6
  74. package/build/devtools/getConnectionInfo.d.ts.map +0 -1
  75. package/build/devtools/getConnectionInfo.native.d.ts +0 -6
  76. package/build/devtools/getConnectionInfo.native.d.ts.map +0 -1
  77. package/build/devtools/index.d.ts +0 -12
  78. package/build/devtools/index.d.ts.map +0 -1
  79. package/build/devtools/logger.d.ts +0 -6
  80. package/build/devtools/logger.d.ts.map +0 -1
  81. package/src/devtools/DevToolsPluginClient.ts +0 -240
  82. package/src/devtools/DevToolsPluginClientFactory.ts +0 -73
  83. package/src/devtools/DevToolsPluginClientImplApp.ts +0 -56
  84. package/src/devtools/DevToolsPluginClientImplBrowser.ts +0 -38
  85. package/src/devtools/MessageFramePacker.ts +0 -235
  86. package/src/devtools/ProtocolVersion.ts +0 -6
  87. package/src/devtools/WebSocketBackingStore.ts +0 -10
  88. package/src/devtools/WebSocketWithReconnect.ts +0 -318
  89. package/src/devtools/__tests__/DevToolsPluginClient-test.ts +0 -285
  90. package/src/devtools/__tests__/DevToolsPluginClientFactory-test.ts +0 -120
  91. package/src/devtools/__tests__/MessageFramePack-test.node.ts +0 -157
  92. package/src/devtools/__tests__/MockWebSocket.ts +0 -100
  93. package/src/devtools/__tests__/WebSocketWithReconnect-test.node.ts +0 -184
  94. package/src/devtools/__tests__/logger-test.ts +0 -20
  95. package/src/devtools/devtools.types.ts +0 -50
  96. package/src/devtools/getConnectionInfo.native.ts +0 -18
  97. package/src/devtools/getConnectionInfo.ts +0 -16
  98. package/src/devtools/index.ts +0 -53
  99. package/src/devtools/logger.ts +0 -29
@@ -1,318 +0,0 @@
1
- import type { DevToolsPluginClientOptions } from './devtools.types';
2
-
3
- export interface Options {
4
- /**
5
- * Reconnect interval in milliseconds.
6
- * @default 1500
7
- */
8
- retriesInterval?: number;
9
-
10
- /**
11
- * The maximum number of retries.
12
- * @default 200
13
- */
14
- maxRetries?: number;
15
-
16
- /**
17
- * The timeout in milliseconds for the WebSocket connecting.
18
- */
19
- connectTimeout?: number;
20
-
21
- /**
22
- * The error handler.
23
- * @default throwing an error
24
- */
25
- onError?: (error: Error) => void;
26
-
27
- /**
28
- * The callback to be called when the WebSocket is reconnected.
29
- * @default no-op
30
- */
31
- onReconnect?: (reason: string) => void;
32
-
33
- /**
34
- * The [`binaryType`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/binaryType).
35
- */
36
- binaryType?: DevToolsPluginClientOptions['websocketBinaryType'];
37
- }
38
-
39
- interface InternalEventListeners {
40
- message?: Set<(event: WebSocketMessageEvent) => void>;
41
- open?: Set<() => void>;
42
- error?: Set<(event: WebSocketErrorEvent) => void>;
43
- close?: Set<(event: WebSocketCloseEvent) => void>;
44
- [eventName: string]: undefined | Set<(event: any) => void>;
45
- }
46
-
47
- export class WebSocketWithReconnect implements WebSocket {
48
- private readonly retriesInterval: number;
49
- private readonly maxRetries: number;
50
- private readonly connectTimeout: number;
51
- private readonly onError: (error: Error) => void;
52
- private readonly onReconnect: (reason: string) => void;
53
-
54
- private ws: WebSocket | null = null;
55
- private retries = 0;
56
- private connectTimeoutHandle: ReturnType<typeof setTimeout> | null = null;
57
- private isClosed = false;
58
- private sendQueue: (string | ArrayBufferView | Blob | ArrayBufferLike)[] = [];
59
- private lastCloseEvent: { code?: number; reason?: string; message?: string } | null = null;
60
- private eventListeners: InternalEventListeners;
61
-
62
- private readonly wsBinaryType?: Options['binaryType'];
63
-
64
- constructor(
65
- public readonly url: string,
66
- options?: Options
67
- ) {
68
- this.retriesInterval = options?.retriesInterval ?? 1500;
69
- this.maxRetries = options?.maxRetries ?? 200;
70
- this.connectTimeout = options?.connectTimeout ?? 5000;
71
- this.onError =
72
- options?.onError ??
73
- ((error) => {
74
- throw error;
75
- });
76
- this.onReconnect = options?.onReconnect ?? (() => {});
77
- this.wsBinaryType = options?.binaryType;
78
- this.eventListeners = Object.create(null);
79
-
80
- this.connect();
81
- }
82
-
83
- public close(code?: number, reason?: string) {
84
- this.clearConnectTimeoutIfNeeded();
85
- this.emitEvent(
86
- 'close',
87
- (this.lastCloseEvent ?? {
88
- code: code ?? 1000,
89
- reason: reason ?? 'Explicit closing',
90
- message: 'Explicit closing',
91
- }) as WebSocketCloseEvent
92
- );
93
- this.lastCloseEvent = null;
94
- this.isClosed = true;
95
- this.eventListeners = Object.create(null);
96
- this.sendQueue = [];
97
- if (this.ws != null) {
98
- const ws = this.ws;
99
- this.ws = null;
100
- this.wsClose(ws);
101
- }
102
- }
103
-
104
- public addEventListener(event: 'message', listener: (event: WebSocketMessageEvent) => void): void;
105
- public addEventListener(event: 'open', listener: () => void): void;
106
- public addEventListener(event: 'error', listener: (event: WebSocketErrorEvent) => void): void;
107
- public addEventListener(event: 'close', listener: (event: WebSocketCloseEvent) => void): void;
108
- public addEventListener(event: string, listener: (event: any) => void) {
109
- const listeners = this.eventListeners[event] || (this.eventListeners[event] = new Set());
110
- listeners.add(listener);
111
- }
112
-
113
- public removeEventListener(event: string, listener: (event: any) => void) {
114
- this.eventListeners[event]?.delete(listener);
115
- }
116
-
117
- //#region Internals
118
-
119
- private connect() {
120
- if (this.ws != null) {
121
- return;
122
- }
123
- this.connectTimeoutHandle = setTimeout(this.handleConnectTimeout, this.connectTimeout);
124
-
125
- this.ws = new WebSocket(this.url.toString());
126
- if (this.wsBinaryType != null) {
127
- this.ws.binaryType = this.wsBinaryType;
128
- }
129
- this.ws.addEventListener('message', this.handleMessage);
130
- this.ws.addEventListener('open', this.handleOpen);
131
- // @ts-ignore TypeScript expects (e: Event) => any, but we want (e: WebSocketErrorEvent) => any
132
- this.ws.addEventListener('error', this.handleError);
133
- this.ws.addEventListener('close', this.handleClose);
134
- }
135
-
136
- public send(data: string | ArrayBufferView | Blob | ArrayBufferLike): void {
137
- if (this.isClosed) {
138
- this.onError(new Error('Unable to send data: WebSocket is closed'));
139
- return;
140
- }
141
-
142
- if (this.retries >= this.maxRetries) {
143
- this.onError(
144
- new Error(`Unable to send data: Exceeded max retries - retries[${this.retries}]`)
145
- );
146
- return;
147
- }
148
-
149
- const ws = this.ws;
150
- if (ws != null && ws.readyState === WebSocket.OPEN) {
151
- ws.send(data);
152
- } else {
153
- this.sendQueue.push(data);
154
- }
155
- }
156
-
157
- private emitEvent(event: 'message', payload: WebSocketMessageEvent): void;
158
- private emitEvent(event: 'open', payload?: void): void;
159
- private emitEvent(event: 'error', payload: WebSocketErrorEvent): void;
160
- private emitEvent(event: 'close', payload: WebSocketCloseEvent): void;
161
- private emitEvent(event: string, payload: any) {
162
- const listeners = this.eventListeners[event];
163
- if (listeners) {
164
- for (const listener of listeners) {
165
- listener(payload);
166
- }
167
- }
168
- }
169
-
170
- private handleOpen = () => {
171
- this.clearConnectTimeoutIfNeeded();
172
- this.lastCloseEvent = null;
173
- this.emitEvent('open');
174
-
175
- const sendQueue = this.sendQueue;
176
- this.sendQueue = [];
177
- for (const data of sendQueue) {
178
- this.send(data);
179
- }
180
- };
181
-
182
- private handleMessage = (event: WebSocketMessageEvent) => {
183
- this.emitEvent('message', event);
184
- };
185
-
186
- private handleError = (event: WebSocketErrorEvent) => {
187
- this.clearConnectTimeoutIfNeeded();
188
- this.emitEvent('error', event);
189
- this.reconnectIfNeeded(`WebSocket error - ${event.message}`);
190
- };
191
-
192
- private handleClose = (event: WebSocketCloseEvent) => {
193
- this.clearConnectTimeoutIfNeeded();
194
- this.lastCloseEvent = {
195
- code: event.code,
196
- reason: event.reason,
197
- message: event.message,
198
- };
199
- this.reconnectIfNeeded(`WebSocket closed - code[${event.code}] reason[${event.reason}]`);
200
- };
201
-
202
- private handleConnectTimeout = () => {
203
- this.reconnectIfNeeded('Timeout from connecting to the WebSocket');
204
- };
205
-
206
- private clearConnectTimeoutIfNeeded() {
207
- if (this.connectTimeoutHandle != null) {
208
- clearTimeout(this.connectTimeoutHandle);
209
- this.connectTimeoutHandle = null;
210
- }
211
- }
212
-
213
- private reconnectIfNeeded(reason: string) {
214
- if (this.ws != null) {
215
- this.wsClose(this.ws);
216
- this.ws = null;
217
- }
218
- if (this.isClosed) {
219
- return;
220
- }
221
-
222
- if (this.retries >= this.maxRetries) {
223
- this.onError(new Error('Exceeded max retries'));
224
- this.close();
225
- return;
226
- }
227
-
228
- setTimeout(() => {
229
- this.retries += 1;
230
- this.connect();
231
- this.onReconnect(reason);
232
- }, this.retriesInterval);
233
- }
234
-
235
- private wsClose(ws: WebSocket) {
236
- try {
237
- ws.removeEventListener('message', this.handleMessage);
238
- ws.removeEventListener('open', this.handleOpen);
239
- ws.removeEventListener('close', this.handleClose);
240
-
241
- // WebSocket throws errors if we don't handle the error event.
242
- // Specifically when closing a ws in CONNECTING readyState,
243
- // WebSocket will have `WebSocket was closed before the connection was established` error.
244
- // We won't like to have the exception, so set a noop error handler.
245
- ws.onerror = () => {};
246
-
247
- ws.close();
248
- } catch {}
249
- }
250
-
251
- public get readyState() {
252
- // Only return closed if the WebSocket is explicitly closed or exceeds max retries.
253
- if (this.isClosed) {
254
- return WebSocket.CLOSED;
255
- }
256
-
257
- const readyState = this.ws?.readyState;
258
- if (readyState === WebSocket.CLOSED) {
259
- return WebSocket.CONNECTING;
260
- }
261
- return readyState ?? WebSocket.CONNECTING;
262
- }
263
-
264
- //#endregion
265
-
266
- //#region WebSocket API proxy
267
-
268
- public readonly CONNECTING = 0;
269
- public readonly OPEN = 1;
270
- public readonly CLOSING = 2;
271
- public readonly CLOSED = 3;
272
-
273
- public get binaryType() {
274
- return this.ws?.binaryType ?? 'blob';
275
- }
276
-
277
- public get bufferedAmount() {
278
- return this.ws?.bufferedAmount ?? 0;
279
- }
280
-
281
- public get extensions() {
282
- return this.ws?.extensions ?? '';
283
- }
284
-
285
- public get protocol() {
286
- return this.ws?.protocol ?? '';
287
- }
288
-
289
- public ping(): void {
290
- return this.ws?.ping();
291
- }
292
-
293
- public dispatchEvent(event: Event) {
294
- return this.ws?.dispatchEvent(event) ?? false;
295
- }
296
-
297
- //#endregion
298
-
299
- //#regions Unsupported legacy properties
300
-
301
- public set onclose(_value: ((e: WebSocketCloseEvent) => any) | null) {
302
- throw new Error('Unsupported legacy property, use addEventListener instead');
303
- }
304
-
305
- public set onerror(_value: ((e: Event) => any) | null) {
306
- throw new Error('Unsupported legacy property, use addEventListener instead');
307
- }
308
-
309
- public set onmessage(_value: ((e: WebSocketMessageEvent) => any) | null) {
310
- throw new Error('Unsupported legacy property, use addEventListener instead');
311
- }
312
-
313
- public set onopen(_value: (() => any) | null) {
314
- throw new Error('Unsupported legacy property, use addEventListener instead');
315
- }
316
-
317
- //#endregion
318
- }
@@ -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
- }