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,6 +0,0 @@
|
|
|
1
|
-
export declare function log(...params: Parameters<typeof console.log>): void;
|
|
2
|
-
export declare function debug(...params: Parameters<typeof console.debug>): void;
|
|
3
|
-
export declare function info(...params: Parameters<typeof console.info>): void;
|
|
4
|
-
export declare function warn(...params: Parameters<typeof console.info>): void;
|
|
5
|
-
export declare function setEnableLogging(enabled: boolean): void;
|
|
6
|
-
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/devtools/logger.ts"],"names":[],"mappings":"AAEA,wBAAgB,GAAG,CAAC,GAAG,MAAM,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,GAAG,CAAC,QAI5D;AAED,wBAAgB,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,KAAK,CAAC,QAIhE;AAED,wBAAgB,IAAI,CAAC,GAAG,MAAM,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,IAAI,CAAC,QAI9D;AAED,wBAAgB,IAAI,CAAC,GAAG,MAAM,EAAE,UAAU,CAAC,OAAO,OAAO,CAAC,IAAI,CAAC,QAI9D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,QAEhD"}
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import { MessageFramePacker } from './MessageFramePacker';
|
|
2
|
-
import { WebSocketBackingStore } from './WebSocketBackingStore';
|
|
3
|
-
import { WebSocketWithReconnect } from './WebSocketWithReconnect';
|
|
4
|
-
import type {
|
|
5
|
-
ConnectionInfo,
|
|
6
|
-
DevToolsPluginClientOptions,
|
|
7
|
-
HandshakeMessageParams,
|
|
8
|
-
} from './devtools.types';
|
|
9
|
-
import * as logger from './logger';
|
|
10
|
-
import { blobToArrayBufferAsync } from '../utils/blobUtils';
|
|
11
|
-
|
|
12
|
-
interface MessageFramePackerMessageKey {
|
|
13
|
-
pluginName: string;
|
|
14
|
-
method: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface EventSubscription {
|
|
18
|
-
remove(): void;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* This client is for the Expo DevTools Plugins to communicate between the app and the DevTools webpage hosted in a browser.
|
|
23
|
-
* All the code should be both compatible with browsers and React Native.
|
|
24
|
-
*/
|
|
25
|
-
export abstract class DevToolsPluginClient {
|
|
26
|
-
private listeners: Record<string, undefined | Set<(params: any) => void>>;
|
|
27
|
-
|
|
28
|
-
private static defaultWSStore: WebSocketBackingStore = new WebSocketBackingStore();
|
|
29
|
-
private readonly wsStore: WebSocketBackingStore = DevToolsPluginClient.defaultWSStore;
|
|
30
|
-
|
|
31
|
-
protected isClosed = false;
|
|
32
|
-
protected retries = 0;
|
|
33
|
-
private readonly messageFramePacker: MessageFramePacker<MessageFramePackerMessageKey> =
|
|
34
|
-
new MessageFramePacker();
|
|
35
|
-
|
|
36
|
-
public constructor(
|
|
37
|
-
public readonly connectionInfo: ConnectionInfo,
|
|
38
|
-
private readonly options?: DevToolsPluginClientOptions
|
|
39
|
-
) {
|
|
40
|
-
this.wsStore = connectionInfo.wsStore || DevToolsPluginClient.defaultWSStore;
|
|
41
|
-
this.listeners = Object.create(null);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Initialize the connection.
|
|
46
|
-
* @hidden
|
|
47
|
-
*/
|
|
48
|
-
public async initAsync(): Promise<void> {
|
|
49
|
-
if (this.wsStore.ws == null) {
|
|
50
|
-
this.wsStore.ws = await this.connectAsync();
|
|
51
|
-
}
|
|
52
|
-
this.wsStore.refCount += 1;
|
|
53
|
-
this.wsStore.ws.addEventListener('message', this.handleMessage);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Close the connection.
|
|
58
|
-
*/
|
|
59
|
-
public async closeAsync(): Promise<void> {
|
|
60
|
-
this.isClosed = true;
|
|
61
|
-
this.wsStore.ws?.removeEventListener('message', this.handleMessage);
|
|
62
|
-
this.wsStore.refCount -= 1;
|
|
63
|
-
if (this.wsStore.refCount < 1) {
|
|
64
|
-
this.wsStore.ws?.close();
|
|
65
|
-
this.wsStore.ws = null;
|
|
66
|
-
}
|
|
67
|
-
this.listeners = Object.create(null);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Send a message to the other end of DevTools.
|
|
72
|
-
* @param method A method name.
|
|
73
|
-
* @param params any extra payload.
|
|
74
|
-
*/
|
|
75
|
-
public sendMessage(method: string, params: any) {
|
|
76
|
-
if (this.wsStore.ws?.readyState === WebSocket.CLOSED) {
|
|
77
|
-
logger.warn('Unable to send message in a disconnected state.');
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
const messageKey: MessageFramePackerMessageKey = {
|
|
81
|
-
pluginName: this.connectionInfo.pluginName,
|
|
82
|
-
method,
|
|
83
|
-
};
|
|
84
|
-
const packedData = this.messageFramePacker.pack({ messageKey, payload: params });
|
|
85
|
-
if (!(packedData instanceof Promise)) {
|
|
86
|
-
this.wsStore.ws?.send(packedData);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
packedData.then((data) => {
|
|
90
|
-
this.wsStore.ws?.send(data);
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Subscribe to a message from the other end of DevTools.
|
|
96
|
-
* @param method Subscribe to a message with a method name.
|
|
97
|
-
* @param listener Listener to be called when a message is received.
|
|
98
|
-
*/
|
|
99
|
-
public addMessageListener(method: string, listener: (params: any) => void): EventSubscription {
|
|
100
|
-
const listenersForMethod = this.listeners[method] || (this.listeners[method] = new Set());
|
|
101
|
-
listenersForMethod.add(listener);
|
|
102
|
-
return {
|
|
103
|
-
remove: () => {
|
|
104
|
-
this.listeners[method]?.delete(listener);
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Subscribe to a message from the other end of DevTools just once.
|
|
111
|
-
* @param method Subscribe to a message with a method name.
|
|
112
|
-
* @param listener Listener to be called when a message is received.
|
|
113
|
-
*/
|
|
114
|
-
public addMessageListenerOnce(method: string, listener: (params: any) => void): void {
|
|
115
|
-
const wrappedListenerOnce = (params: any): void => {
|
|
116
|
-
listener(params);
|
|
117
|
-
this.listeners[method]?.delete(wrappedListenerOnce);
|
|
118
|
-
};
|
|
119
|
-
this.addMessageListener(method, wrappedListenerOnce);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Internal handshake message sender.
|
|
124
|
-
* @hidden
|
|
125
|
-
*/
|
|
126
|
-
protected sendHandshakeMessage(params: HandshakeMessageParams) {
|
|
127
|
-
if (this.wsStore.ws?.readyState === WebSocket.CLOSED) {
|
|
128
|
-
logger.warn('Unable to send message in a disconnected state.');
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
this.wsStore.ws?.send(JSON.stringify({ ...params, __isHandshakeMessages: true }));
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Internal handshake message listener.
|
|
136
|
-
* @hidden
|
|
137
|
-
*/
|
|
138
|
-
protected addHandskakeMessageListener(
|
|
139
|
-
listener: (params: HandshakeMessageParams) => void
|
|
140
|
-
): EventSubscription {
|
|
141
|
-
const messageListener = (event: MessageEvent) => {
|
|
142
|
-
if (typeof event.data !== 'string') {
|
|
143
|
-
// binary data is not coming from the handshake messages.
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const data = JSON.parse(event.data);
|
|
148
|
-
if (!data.__isHandshakeMessages) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
delete data.__isHandshakeMessages;
|
|
152
|
-
const params = data as HandshakeMessageParams;
|
|
153
|
-
if (params.pluginName && params.pluginName !== this.connectionInfo.pluginName) {
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
listener(params);
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
this.wsStore.ws?.addEventListener('message', messageListener);
|
|
160
|
-
return {
|
|
161
|
-
remove: () => {
|
|
162
|
-
this.wsStore.ws?.removeEventListener('message', messageListener);
|
|
163
|
-
},
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Returns whether the client is connected to the server.
|
|
169
|
-
*/
|
|
170
|
-
public isConnected(): boolean {
|
|
171
|
-
return this.wsStore.ws?.readyState === WebSocket.OPEN;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* The method to create the WebSocket connection.
|
|
176
|
-
*/
|
|
177
|
-
protected connectAsync(): Promise<WebSocket> {
|
|
178
|
-
return new Promise((resolve, reject) => {
|
|
179
|
-
const endpoint = 'expo-dev-plugins/broadcast';
|
|
180
|
-
const ws = new WebSocketWithReconnect(`ws://${this.connectionInfo.devServer}/${endpoint}`, {
|
|
181
|
-
binaryType: this.options?.websocketBinaryType,
|
|
182
|
-
onError: (e: unknown) => {
|
|
183
|
-
if (e instanceof Error) {
|
|
184
|
-
console.warn(`Error happened from the WebSocket connection: ${e.message}\n${e.stack}`);
|
|
185
|
-
} else {
|
|
186
|
-
console.warn(`Error happened from the WebSocket connection: ${JSON.stringify(e)}`);
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
});
|
|
190
|
-
ws.addEventListener('open', () => {
|
|
191
|
-
resolve(ws);
|
|
192
|
-
});
|
|
193
|
-
ws.addEventListener('error', (e) => {
|
|
194
|
-
reject(e);
|
|
195
|
-
});
|
|
196
|
-
ws.addEventListener('close', (e: WebSocketCloseEvent) => {
|
|
197
|
-
logger.info('WebSocket closed', e.code, e.reason);
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
protected handleMessage = async (event: WebSocketMessageEvent) => {
|
|
203
|
-
let data: ArrayBuffer | string;
|
|
204
|
-
if (typeof event.data === 'string') {
|
|
205
|
-
data = event.data;
|
|
206
|
-
} else if (event.data instanceof ArrayBuffer) {
|
|
207
|
-
data = event.data;
|
|
208
|
-
} else if (ArrayBuffer.isView(event.data)) {
|
|
209
|
-
data = event.data.buffer as ArrayBuffer;
|
|
210
|
-
} else if (event.data instanceof Blob) {
|
|
211
|
-
data = await blobToArrayBufferAsync(event.data);
|
|
212
|
-
} else {
|
|
213
|
-
logger.warn('Unsupported received data type in handleMessageImpl');
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
const { messageKey, payload, ...rest } = this.messageFramePacker.unpack(data);
|
|
217
|
-
// @ts-expect-error: `__isHandshakeMessages` is a private field that is not part of the MessageFramePacker type.
|
|
218
|
-
if (rest?.__isHandshakeMessages === true) {
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
if (messageKey.pluginName && messageKey.pluginName !== this.connectionInfo.pluginName) {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const listenersForMethod = this.listeners[messageKey.method];
|
|
226
|
-
if (listenersForMethod) {
|
|
227
|
-
for (const listener of listenersForMethod) {
|
|
228
|
-
listener(payload);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Get the WebSocket backing store. Exposed for testing.
|
|
235
|
-
* @hidden
|
|
236
|
-
*/
|
|
237
|
-
public getWebSocketBackingStore(): WebSocketBackingStore {
|
|
238
|
-
return this.wsStore;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import type { DevToolsPluginClient } from './DevToolsPluginClient';
|
|
2
|
-
import { DevToolsPluginClientImplApp } from './DevToolsPluginClientImplApp';
|
|
3
|
-
import { DevToolsPluginClientImplBrowser } from './DevToolsPluginClientImplBrowser';
|
|
4
|
-
import type { ConnectionInfo, DevToolsPluginClientOptions } from './devtools.types';
|
|
5
|
-
import { getConnectionInfo } from './getConnectionInfo';
|
|
6
|
-
|
|
7
|
-
const instanceMap: Record<string, DevToolsPluginClient | Promise<DevToolsPluginClient>> = {};
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Factory of DevToolsPluginClient based on sender types.
|
|
11
|
-
* @hidden
|
|
12
|
-
*/
|
|
13
|
-
export async function createDevToolsPluginClient(
|
|
14
|
-
connectionInfo: ConnectionInfo,
|
|
15
|
-
options?: DevToolsPluginClientOptions
|
|
16
|
-
): Promise<DevToolsPluginClient> {
|
|
17
|
-
let client: DevToolsPluginClient;
|
|
18
|
-
if (connectionInfo.sender === 'app') {
|
|
19
|
-
client = new DevToolsPluginClientImplApp(connectionInfo, options);
|
|
20
|
-
} else {
|
|
21
|
-
client = new DevToolsPluginClientImplBrowser(connectionInfo, options);
|
|
22
|
-
}
|
|
23
|
-
await client.initAsync();
|
|
24
|
-
return client;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Public API to get the DevToolsPluginClient instance.
|
|
29
|
-
*/
|
|
30
|
-
export async function getDevToolsPluginClientAsync(
|
|
31
|
-
pluginName: string,
|
|
32
|
-
options?: DevToolsPluginClientOptions
|
|
33
|
-
): Promise<DevToolsPluginClient> {
|
|
34
|
-
const connectionInfo = getConnectionInfo();
|
|
35
|
-
|
|
36
|
-
let instance: DevToolsPluginClient | Promise<DevToolsPluginClient> | null =
|
|
37
|
-
instanceMap[pluginName];
|
|
38
|
-
if (instance != null) {
|
|
39
|
-
if (instance instanceof Promise) {
|
|
40
|
-
return instance;
|
|
41
|
-
}
|
|
42
|
-
if (
|
|
43
|
-
instance.isConnected() === false ||
|
|
44
|
-
instance.connectionInfo.devServer !== connectionInfo.devServer
|
|
45
|
-
) {
|
|
46
|
-
await instance.closeAsync();
|
|
47
|
-
delete instanceMap[pluginName];
|
|
48
|
-
instance = null;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (instance == null) {
|
|
52
|
-
const instancePromise = createDevToolsPluginClient({ ...connectionInfo, pluginName }, options);
|
|
53
|
-
instanceMap[pluginName] = instancePromise;
|
|
54
|
-
instance = await instancePromise;
|
|
55
|
-
instanceMap[pluginName] = instance;
|
|
56
|
-
}
|
|
57
|
-
return instance;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Internal testing API to cleanup all DevToolsPluginClient instances.
|
|
62
|
-
*/
|
|
63
|
-
export function cleanupDevToolsPluginInstances() {
|
|
64
|
-
for (const pluginName of Object.keys(instanceMap)) {
|
|
65
|
-
const instance = instanceMap[pluginName];
|
|
66
|
-
delete instanceMap[pluginName];
|
|
67
|
-
if (instance instanceof Promise) {
|
|
68
|
-
instance.then((instance) => instance.closeAsync());
|
|
69
|
-
} else {
|
|
70
|
-
instance.closeAsync();
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { DevToolsPluginClient } from './DevToolsPluginClient';
|
|
2
|
-
import * as logger from './logger';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* The DevToolsPluginClient for the app -> browser communication.
|
|
6
|
-
*/
|
|
7
|
-
export class DevToolsPluginClientImplApp extends DevToolsPluginClient {
|
|
8
|
-
// Map of pluginName -> browserClientId
|
|
9
|
-
private browserClientMap: Record<string, string> = {};
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Initialize the connection.
|
|
13
|
-
* @hidden
|
|
14
|
-
*/
|
|
15
|
-
override async initAsync(): Promise<void> {
|
|
16
|
-
await super.initAsync();
|
|
17
|
-
this.addHandshakeHandler();
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
private addHandshakeHandler() {
|
|
21
|
-
this.addHandskakeMessageListener((params) => {
|
|
22
|
-
if (params.method === 'handshake') {
|
|
23
|
-
const { pluginName, protocolVersion } = params;
|
|
24
|
-
|
|
25
|
-
// [0] Check protocol version
|
|
26
|
-
if (protocolVersion !== this.connectionInfo.protocolVersion) {
|
|
27
|
-
// Use console.warn than logger because we want to show the warning even logging is disabled.
|
|
28
|
-
console.warn(
|
|
29
|
-
`Received an incompatible devtools plugin handshake message - pluginName[${pluginName}]`
|
|
30
|
-
);
|
|
31
|
-
this.terminateBrowserClient(pluginName, params.browserClientId);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// [1] Terminate duplicated browser clients for the same plugin
|
|
36
|
-
const previousBrowserClientId = this.browserClientMap[pluginName];
|
|
37
|
-
if (previousBrowserClientId != null && previousBrowserClientId !== params.browserClientId) {
|
|
38
|
-
logger.info(
|
|
39
|
-
`Terminate the previous browser client connection - previousBrowserClientId[${previousBrowserClientId}]`
|
|
40
|
-
);
|
|
41
|
-
this.terminateBrowserClient(pluginName, previousBrowserClientId);
|
|
42
|
-
}
|
|
43
|
-
this.browserClientMap[pluginName] = params.browserClientId;
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
private terminateBrowserClient(pluginName: string, browserClientId: string) {
|
|
49
|
-
this.sendHandshakeMessage({
|
|
50
|
-
protocolVersion: this.connectionInfo.protocolVersion,
|
|
51
|
-
method: 'terminateBrowserClient',
|
|
52
|
-
browserClientId,
|
|
53
|
-
pluginName,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { DevToolsPluginClient } from './DevToolsPluginClient';
|
|
2
|
-
import * as logger from './logger';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* The DevToolsPluginClient for the browser -> app communication.
|
|
6
|
-
*/
|
|
7
|
-
export class DevToolsPluginClientImplBrowser extends DevToolsPluginClient {
|
|
8
|
-
private browserClientId: string = Date.now().toString();
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Initialize the connection.
|
|
12
|
-
* @hidden
|
|
13
|
-
*/
|
|
14
|
-
override async initAsync(): Promise<void> {
|
|
15
|
-
await super.initAsync();
|
|
16
|
-
this.startHandshake();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
private startHandshake() {
|
|
20
|
-
this.addHandskakeMessageListener((params) => {
|
|
21
|
-
if (
|
|
22
|
-
params.method === 'terminateBrowserClient' &&
|
|
23
|
-
this.browserClientId === params.browserClientId
|
|
24
|
-
) {
|
|
25
|
-
logger.info(
|
|
26
|
-
'Received terminateBrowserClient messages and terminate the current connection'
|
|
27
|
-
);
|
|
28
|
-
this.closeAsync();
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
this.sendHandshakeMessage({
|
|
32
|
-
protocolVersion: this.connectionInfo.protocolVersion,
|
|
33
|
-
pluginName: this.connectionInfo.pluginName,
|
|
34
|
-
method: 'handshake',
|
|
35
|
-
browserClientId: this.browserClientId,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A message frame packer that serializes a messageKey and a payload into either a JSON string
|
|
3
|
-
* (fast path) or a binary format (for complex payloads).
|
|
4
|
-
*
|
|
5
|
-
* Fast Path (JSON.stringify/JSON.parse):
|
|
6
|
-
* - For simple payloads (e.g., strings, numbers, null, undefined, or plain objects), the packer
|
|
7
|
-
* uses `JSON.stringify` for serialization and `JSON.parse` for deserialization, ensuring
|
|
8
|
-
* optimal performance.
|
|
9
|
-
*
|
|
10
|
-
* Binary Format:
|
|
11
|
-
* - For more complex payloads (e.g., Uint8Array, ArrayBuffer, Blob), the packer uses a binary
|
|
12
|
-
* format with the following structure:
|
|
13
|
-
*
|
|
14
|
-
* +------------------+-------------------+----------------------------+--------------------------+
|
|
15
|
-
* | 4 bytes (Uint32) | Variable length | 1 byte (Uint8) | Variable length |
|
|
16
|
-
* | MessageKeyLength | MessageKey (JSON) | PayloadTypeIndicator (enum)| Payload (binary data) |
|
|
17
|
-
* +------------------+-------------------+----------------------------+--------------------------+
|
|
18
|
-
*
|
|
19
|
-
* 1. MessageKeyLength (4 bytes):
|
|
20
|
-
* - A 4-byte unsigned integer indicating the length of the MessageKey JSON string.
|
|
21
|
-
*
|
|
22
|
-
* 2. MessageKey (Variable length):
|
|
23
|
-
* - The JSON string representing the message key, encoded as UTF-8.
|
|
24
|
-
*
|
|
25
|
-
* 3. PayloadTypeIndicator (1 byte):
|
|
26
|
-
* - A single byte enum value representing the type of the payload (e.g., Uint8Array, String,
|
|
27
|
-
* Object, ArrayBuffer, Blob).
|
|
28
|
-
*
|
|
29
|
-
* 4. Payload (Variable length):
|
|
30
|
-
* - The actual payload data, which can vary in type and length depending on the PayloadType.
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
import { blobToArrayBufferAsync } from '../utils/blobUtils';
|
|
34
|
-
|
|
35
|
-
type MessageKeyTypeBase = string | object;
|
|
36
|
-
type PayloadType = Uint8Array | string | number | null | undefined | object | ArrayBuffer | Blob;
|
|
37
|
-
|
|
38
|
-
enum PayloadTypeIndicator {
|
|
39
|
-
Uint8Array = 1,
|
|
40
|
-
String = 2,
|
|
41
|
-
Number = 3,
|
|
42
|
-
Null = 4,
|
|
43
|
-
Undefined = 5,
|
|
44
|
-
Object = 6,
|
|
45
|
-
ArrayBuffer = 7,
|
|
46
|
-
Blob = 8,
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface MessageFrame<T extends MessageKeyTypeBase> {
|
|
50
|
-
messageKey: T;
|
|
51
|
-
payload?: PayloadType;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export class MessageFramePacker<T extends MessageKeyTypeBase> {
|
|
55
|
-
private textEncoder = new TextEncoder();
|
|
56
|
-
private textDecoder = new TextDecoder();
|
|
57
|
-
|
|
58
|
-
public pack({ messageKey, payload }: MessageFrame<T>): string | Uint8Array | Promise<Uint8Array> {
|
|
59
|
-
// Fast path to pack as string given `JSON.stringify` is fast.
|
|
60
|
-
if (this.isFastPathPayload(payload)) {
|
|
61
|
-
return JSON.stringify({ messageKey, payload });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Slowest path for Blob returns a promise.
|
|
65
|
-
if (payload instanceof Blob) {
|
|
66
|
-
return new Promise(async (resolve, reject) => {
|
|
67
|
-
try {
|
|
68
|
-
const arrayBuffer = await blobToArrayBufferAsync(payload);
|
|
69
|
-
resolve(
|
|
70
|
-
this.packImpl(
|
|
71
|
-
{ messageKey, payload: new Uint8Array(arrayBuffer) },
|
|
72
|
-
PayloadTypeIndicator.Blob
|
|
73
|
-
)
|
|
74
|
-
);
|
|
75
|
-
} catch (error) {
|
|
76
|
-
reject(error);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Slow path for other types returns a Uint8Array.
|
|
82
|
-
return this.packImpl({ messageKey, payload }, undefined);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
public unpack(packedData: string | ArrayBuffer): MessageFrame<T> {
|
|
86
|
-
// Fast path to unpack as string given `JSON.parse` is fast.
|
|
87
|
-
if (typeof packedData === 'string') {
|
|
88
|
-
return JSON.parse(packedData);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// [0] messageKeyLength (4 bytes)
|
|
92
|
-
const messageKeyLengthView = new DataView(packedData, 0, 4);
|
|
93
|
-
const messageKeyLength = messageKeyLengthView.getUint32(0, false);
|
|
94
|
-
|
|
95
|
-
// [1] messageKey (variable length)
|
|
96
|
-
const messageKeyBytes = packedData.slice(4, 4 + messageKeyLength);
|
|
97
|
-
const messageKeyString = this.textDecoder.decode(messageKeyBytes);
|
|
98
|
-
const messageKey = JSON.parse(messageKeyString);
|
|
99
|
-
|
|
100
|
-
// [2] payloadTypeIndicator (1 byte)
|
|
101
|
-
const payloadTypeView = new DataView(packedData, 4 + messageKeyLength, 1);
|
|
102
|
-
const payloadType = payloadTypeView.getUint8(0);
|
|
103
|
-
|
|
104
|
-
// [3] payload (variable length)
|
|
105
|
-
const payloadBinary = packedData.slice(4 + messageKeyLength + 1);
|
|
106
|
-
const payload = this.deserializePayload(payloadBinary, payloadType);
|
|
107
|
-
|
|
108
|
-
return { messageKey, payload };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
private isFastPathPayload(payload: PayloadType): boolean {
|
|
112
|
-
if (payload == null) {
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
const payloadType = typeof payload;
|
|
116
|
-
if (payloadType === 'string' || payloadType === 'number') {
|
|
117
|
-
return true;
|
|
118
|
-
}
|
|
119
|
-
if (payloadType === 'object' && payload.constructor === Object) {
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
private payloadToUint8Array(payload: PayloadType): Uint8Array {
|
|
126
|
-
if (payload instanceof Uint8Array) {
|
|
127
|
-
return payload;
|
|
128
|
-
} else if (typeof payload === 'string') {
|
|
129
|
-
return this.textEncoder.encode(payload);
|
|
130
|
-
} else if (typeof payload === 'number') {
|
|
131
|
-
const buffer = new ArrayBuffer(8);
|
|
132
|
-
const view = new DataView(buffer);
|
|
133
|
-
view.setFloat64(0, payload, false);
|
|
134
|
-
return new Uint8Array(buffer);
|
|
135
|
-
} else if (payload === null) {
|
|
136
|
-
return new Uint8Array(0);
|
|
137
|
-
} else if (payload === undefined) {
|
|
138
|
-
return new Uint8Array(0);
|
|
139
|
-
} else if (payload instanceof ArrayBuffer) {
|
|
140
|
-
return new Uint8Array(payload);
|
|
141
|
-
} else if (payload instanceof Blob) {
|
|
142
|
-
throw new Error('Blob is not supported in this callsite.');
|
|
143
|
-
} else {
|
|
144
|
-
return this.textEncoder.encode(JSON.stringify(payload));
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
private packImpl(
|
|
149
|
-
{ messageKey, payload }: MessageFrame<T>,
|
|
150
|
-
payloadType: PayloadTypeIndicator | undefined
|
|
151
|
-
): Promise<Uint8Array> | Uint8Array {
|
|
152
|
-
const messageKeyString = JSON.stringify(messageKey);
|
|
153
|
-
const messageKeyBytes = this.textEncoder.encode(messageKeyString);
|
|
154
|
-
const messageKeyLength = messageKeyBytes.length;
|
|
155
|
-
const payloadBinary = this.payloadToUint8Array(payload);
|
|
156
|
-
|
|
157
|
-
const totalLength = 4 + messageKeyLength + 1 + payloadBinary.byteLength;
|
|
158
|
-
const buffer = new ArrayBuffer(totalLength);
|
|
159
|
-
const packedArray = new Uint8Array(buffer);
|
|
160
|
-
|
|
161
|
-
// [0] messageKeyLength (4 bytes)
|
|
162
|
-
const messageKeyLengthView = new DataView(buffer, 0, 4);
|
|
163
|
-
messageKeyLengthView.setUint32(0, messageKeyLength, false);
|
|
164
|
-
|
|
165
|
-
// [1] messageKey (variable length)
|
|
166
|
-
packedArray.set(messageKeyBytes, 4);
|
|
167
|
-
|
|
168
|
-
// [2] payloadTypeIndicator (1 byte)
|
|
169
|
-
const payloadTypeView = new DataView(buffer, 4 + messageKeyLength, 1);
|
|
170
|
-
payloadTypeView.setUint8(0, payloadType ?? MessageFramePacker.getPayloadTypeIndicator(payload));
|
|
171
|
-
|
|
172
|
-
// [3] payload (variable length)
|
|
173
|
-
packedArray.set(payloadBinary, 4 + messageKeyLength + 1);
|
|
174
|
-
|
|
175
|
-
return packedArray;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
private deserializePayload(
|
|
179
|
-
payloadBinary: ArrayBuffer,
|
|
180
|
-
payloadTypeIndicator: PayloadTypeIndicator
|
|
181
|
-
): PayloadType {
|
|
182
|
-
switch (payloadTypeIndicator) {
|
|
183
|
-
case PayloadTypeIndicator.Uint8Array: {
|
|
184
|
-
return new Uint8Array(payloadBinary);
|
|
185
|
-
}
|
|
186
|
-
case PayloadTypeIndicator.String: {
|
|
187
|
-
return this.textDecoder.decode(payloadBinary);
|
|
188
|
-
}
|
|
189
|
-
case PayloadTypeIndicator.Number: {
|
|
190
|
-
const view = new DataView(payloadBinary);
|
|
191
|
-
return view.getFloat64(0, false);
|
|
192
|
-
}
|
|
193
|
-
case PayloadTypeIndicator.Null: {
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
case PayloadTypeIndicator.Undefined: {
|
|
197
|
-
return undefined;
|
|
198
|
-
}
|
|
199
|
-
case PayloadTypeIndicator.Object: {
|
|
200
|
-
const jsonString = this.textDecoder.decode(payloadBinary);
|
|
201
|
-
return JSON.parse(jsonString);
|
|
202
|
-
}
|
|
203
|
-
case PayloadTypeIndicator.ArrayBuffer: {
|
|
204
|
-
return payloadBinary;
|
|
205
|
-
}
|
|
206
|
-
case PayloadTypeIndicator.Blob: {
|
|
207
|
-
return new Blob([payloadBinary]);
|
|
208
|
-
}
|
|
209
|
-
default:
|
|
210
|
-
throw new Error('Unsupported payload type');
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
private static getPayloadTypeIndicator(payload: PayloadType): PayloadTypeIndicator {
|
|
215
|
-
if (payload instanceof Uint8Array) {
|
|
216
|
-
return PayloadTypeIndicator.Uint8Array;
|
|
217
|
-
} else if (typeof payload === 'string') {
|
|
218
|
-
return PayloadTypeIndicator.String;
|
|
219
|
-
} else if (typeof payload === 'number') {
|
|
220
|
-
return PayloadTypeIndicator.Number;
|
|
221
|
-
} else if (payload === null) {
|
|
222
|
-
return PayloadTypeIndicator.Null;
|
|
223
|
-
} else if (payload === undefined) {
|
|
224
|
-
return PayloadTypeIndicator.Undefined;
|
|
225
|
-
} else if (payload instanceof ArrayBuffer) {
|
|
226
|
-
return PayloadTypeIndicator.ArrayBuffer;
|
|
227
|
-
} else if (payload instanceof Blob) {
|
|
228
|
-
return PayloadTypeIndicator.Blob;
|
|
229
|
-
} else if (typeof payload === 'object') {
|
|
230
|
-
return PayloadTypeIndicator.Object;
|
|
231
|
-
} else {
|
|
232
|
-
throw new Error('Unsupported payload type');
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A transport protocol version between the app and the webui.
|
|
3
|
-
* It shows a warning in handshaking stage if the version is different between the app and the webui.
|
|
4
|
-
* The value should be increased when the protocol is changed.
|
|
5
|
-
*/
|
|
6
|
-
export const PROTOCOL_VERSION = 1;
|