@expo/devtools 0.0.1

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 (63) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +16 -0
  3. package/build/DevToolsPluginClient.d.ts +73 -0
  4. package/build/DevToolsPluginClient.d.ts.map +1 -0
  5. package/build/DevToolsPluginClient.js +212 -0
  6. package/build/DevToolsPluginClient.js.map +1 -0
  7. package/build/DevToolsPluginClientFactory.d.ts +16 -0
  8. package/build/DevToolsPluginClientFactory.d.ts.map +1 -0
  9. package/build/DevToolsPluginClientFactory.js +60 -0
  10. package/build/DevToolsPluginClientFactory.js.map +1 -0
  11. package/build/DevToolsPluginClientImplApp.d.ts +15 -0
  12. package/build/DevToolsPluginClientImplApp.d.ts.map +1 -0
  13. package/build/DevToolsPluginClientImplApp.js +47 -0
  14. package/build/DevToolsPluginClientImplApp.js.map +1 -0
  15. package/build/DevToolsPluginClientImplBrowser.d.ts +14 -0
  16. package/build/DevToolsPluginClientImplBrowser.d.ts.map +1 -0
  17. package/build/DevToolsPluginClientImplBrowser.js +32 -0
  18. package/build/DevToolsPluginClientImplBrowser.js.map +1 -0
  19. package/build/MessageFramePacker.d.ts +50 -0
  20. package/build/MessageFramePacker.d.ts.map +1 -0
  21. package/build/MessageFramePacker.js +211 -0
  22. package/build/MessageFramePacker.js.map +1 -0
  23. package/build/ProtocolVersion.d.ts +7 -0
  24. package/build/ProtocolVersion.d.ts.map +1 -0
  25. package/build/ProtocolVersion.js +7 -0
  26. package/build/ProtocolVersion.js.map +1 -0
  27. package/build/WebSocketBackingStore.d.ts +10 -0
  28. package/build/WebSocketBackingStore.d.ts.map +1 -0
  29. package/build/WebSocketBackingStore.js +13 -0
  30. package/build/WebSocketBackingStore.js.map +1 -0
  31. package/build/WebSocketWithReconnect.d.ts +92 -0
  32. package/build/WebSocketWithReconnect.d.ts.map +1 -0
  33. package/build/WebSocketWithReconnect.js +216 -0
  34. package/build/WebSocketWithReconnect.js.map +1 -0
  35. package/build/devtools.types.d.ts +42 -0
  36. package/build/devtools.types.d.ts.map +1 -0
  37. package/build/devtools.types.js +2 -0
  38. package/build/devtools.types.js.map +1 -0
  39. package/build/getConnectionInfo.d.ts +6 -0
  40. package/build/getConnectionInfo.d.ts.map +1 -0
  41. package/build/getConnectionInfo.js +14 -0
  42. package/build/getConnectionInfo.js.map +1 -0
  43. package/build/getConnectionInfo.native.d.ts +6 -0
  44. package/build/getConnectionInfo.native.d.ts.map +1 -0
  45. package/build/getConnectionInfo.native.js +16 -0
  46. package/build/getConnectionInfo.native.js.map +1 -0
  47. package/build/hooks.d.ts +7 -0
  48. package/build/hooks.d.ts.map +1 -0
  49. package/build/hooks.js +37 -0
  50. package/build/hooks.js.map +1 -0
  51. package/build/index.d.ts +6 -0
  52. package/build/index.d.ts.map +1 -0
  53. package/build/index.js +5 -0
  54. package/build/index.js.map +1 -0
  55. package/build/logger.d.ts +6 -0
  56. package/build/logger.d.ts.map +1 -0
  57. package/build/logger.js +25 -0
  58. package/build/logger.js.map +1 -0
  59. package/build/utils/blobUtils.d.ts +9 -0
  60. package/build/utils/blobUtils.d.ts.map +1 -0
  61. package/build/utils/blobUtils.js +23 -0
  62. package/build/utils/blobUtils.js.map +1 -0
  63. package/package.json +64 -0
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-present 650 Industries, Inc. (aka Expo)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,16 @@
1
+ <!-- Title -->
2
+ <h1 align="center">
3
+ 👋 Welcome to <br><code>@expo/devtools</code>
4
+ </h1>
5
+
6
+ <p align="center">DevTools plugin helpers for Expo.</p>
7
+
8
+ <p align="center">
9
+ <img src="https://flat.badgen.net/packagephobia/install/@expo/devtools">
10
+
11
+ <a href="https://www.npmjs.com/package/@expo/devtools">
12
+ <img src="https://flat.badgen.net/npm/dw/@expo/devtools" target="_blank" />
13
+ </a>
14
+ </p>
15
+
16
+ <!-- Body -->
@@ -0,0 +1,73 @@
1
+ import { WebSocketBackingStore } from './WebSocketBackingStore';
2
+ import { type WebSocketMessageEvent } from './WebSocketWithReconnect';
3
+ import type { ConnectionInfo, DevToolsPluginClientOptions, HandshakeMessageParams } from './devtools.types';
4
+ export interface EventSubscription {
5
+ remove(): void;
6
+ }
7
+ /**
8
+ * This client is for the Expo DevTools Plugins to communicate between the app and the DevTools webpage hosted in a browser.
9
+ * All the code should be both compatible with browsers and React Native.
10
+ */
11
+ export declare abstract class DevToolsPluginClient {
12
+ readonly connectionInfo: ConnectionInfo;
13
+ private readonly options?;
14
+ private listeners;
15
+ private static defaultWSStore;
16
+ private readonly wsStore;
17
+ protected isClosed: boolean;
18
+ protected retries: number;
19
+ private readonly messageFramePacker;
20
+ constructor(connectionInfo: ConnectionInfo, options?: DevToolsPluginClientOptions | undefined);
21
+ /**
22
+ * Initialize the connection.
23
+ * @hidden
24
+ */
25
+ initAsync(): Promise<void>;
26
+ /**
27
+ * Close the connection.
28
+ */
29
+ closeAsync(): Promise<void>;
30
+ /**
31
+ * Send a message to the other end of DevTools.
32
+ * @param method A method name.
33
+ * @param params any extra payload.
34
+ */
35
+ sendMessage(method: string, params: any): void;
36
+ /**
37
+ * Subscribe to a message from the other end of DevTools.
38
+ * @param method Subscribe to a message with a method name.
39
+ * @param listener Listener to be called when a message is received.
40
+ */
41
+ addMessageListener(method: string, listener: (params: any) => void): EventSubscription;
42
+ /**
43
+ * Subscribe to a message from the other end of DevTools just once.
44
+ * @param method Subscribe to a message with a method name.
45
+ * @param listener Listener to be called when a message is received.
46
+ */
47
+ addMessageListenerOnce(method: string, listener: (params: any) => void): void;
48
+ /**
49
+ * Internal handshake message sender.
50
+ * @hidden
51
+ */
52
+ protected sendHandshakeMessage(params: HandshakeMessageParams): void;
53
+ /**
54
+ * Internal handshake message listener.
55
+ * @hidden
56
+ */
57
+ protected addHandskakeMessageListener(listener: (params: HandshakeMessageParams) => void): EventSubscription;
58
+ /**
59
+ * Returns whether the client is connected to the server.
60
+ */
61
+ isConnected(): boolean;
62
+ /**
63
+ * The method to create the WebSocket connection.
64
+ */
65
+ protected connectAsync(): Promise<WebSocket>;
66
+ protected handleMessage: (event: WebSocketMessageEvent) => Promise<void>;
67
+ /**
68
+ * Get the WebSocket backing store. Exposed for testing.
69
+ * @hidden
70
+ */
71
+ getWebSocketBackingStore(): WebSocketBackingStore;
72
+ }
73
+ //# sourceMappingURL=DevToolsPluginClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DevToolsPluginClient.d.ts","sourceRoot":"","sources":["../src/DevToolsPluginClient.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EACV,cAAc,EACd,2BAA2B,EAC3B,sBAAsB,EACvB,MAAM,kBAAkB,CAAC;AAS1B,MAAM,WAAW,iBAAiB;IAChC,MAAM,IAAI,IAAI,CAAC;CAChB;AAED;;;GAGG;AACH,8BAAsB,oBAAoB;aAYtB,cAAc,EAAE,cAAc;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IAZ3B,OAAO,CAAC,SAAS,CAAyD;IAE1E,OAAO,CAAC,MAAM,CAAC,cAAc,CAAsD;IACnF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA8D;IAEtF,SAAS,CAAC,QAAQ,UAAS;IAC3B,SAAS,CAAC,OAAO,SAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CACR;gBAGT,cAAc,EAAE,cAAc,EAC7B,OAAO,CAAC,EAAE,2BAA2B,YAAA;IAMxD;;;OAGG;IACU,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAQvC;;OAEG;IACU,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAWxC;;;;OAIG;IACI,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG;IAmB9C;;;;OAIG;IACI,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,GAAG,iBAAiB;IAU7F;;;;OAIG;IACI,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAQpF;;;OAGG;IACH,SAAS,CAAC,oBAAoB,CAAC,MAAM,EAAE,sBAAsB;IAQ7D;;;OAGG;IACH,SAAS,CAAC,2BAA2B,CACnC,QAAQ,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,GACjD,iBAAiB;IA2BpB;;OAEG;IACI,WAAW,IAAI,OAAO;IAI7B;;OAEG;IACH,SAAS,CAAC,YAAY,IAAI,OAAO,CAAC,SAAS,CAAC;IAyB5C,SAAS,CAAC,aAAa,GAAU,OAAO,qBAAqB,mBA6B3D;IAEF;;;OAGG;IACI,wBAAwB,IAAI,qBAAqB;CAGzD"}
@@ -0,0 +1,212 @@
1
+ import { MessageFramePacker } from './MessageFramePacker';
2
+ import { WebSocketBackingStore } from './WebSocketBackingStore';
3
+ import { WebSocketWithReconnect, } from './WebSocketWithReconnect';
4
+ import * as logger from './logger';
5
+ import { blobToArrayBufferAsync } from './utils/blobUtils';
6
+ /**
7
+ * This client is for the Expo DevTools Plugins to communicate between the app and the DevTools webpage hosted in a browser.
8
+ * All the code should be both compatible with browsers and React Native.
9
+ */
10
+ export class DevToolsPluginClient {
11
+ connectionInfo;
12
+ options;
13
+ listeners;
14
+ static defaultWSStore = new WebSocketBackingStore();
15
+ wsStore = DevToolsPluginClient.defaultWSStore;
16
+ isClosed = false;
17
+ retries = 0;
18
+ messageFramePacker = new MessageFramePacker();
19
+ constructor(connectionInfo, options) {
20
+ this.connectionInfo = connectionInfo;
21
+ this.options = options;
22
+ this.wsStore = connectionInfo.wsStore || DevToolsPluginClient.defaultWSStore;
23
+ this.listeners = Object.create(null);
24
+ }
25
+ /**
26
+ * Initialize the connection.
27
+ * @hidden
28
+ */
29
+ async initAsync() {
30
+ if (this.wsStore.ws == null) {
31
+ this.wsStore.ws = await this.connectAsync();
32
+ }
33
+ this.wsStore.refCount += 1;
34
+ this.wsStore.ws.addEventListener('message', this.handleMessage);
35
+ }
36
+ /**
37
+ * Close the connection.
38
+ */
39
+ async closeAsync() {
40
+ this.isClosed = true;
41
+ this.wsStore.ws?.removeEventListener('message', this.handleMessage);
42
+ this.wsStore.refCount -= 1;
43
+ if (this.wsStore.refCount < 1) {
44
+ this.wsStore.ws?.close();
45
+ this.wsStore.ws = null;
46
+ }
47
+ this.listeners = Object.create(null);
48
+ }
49
+ /**
50
+ * Send a message to the other end of DevTools.
51
+ * @param method A method name.
52
+ * @param params any extra payload.
53
+ */
54
+ sendMessage(method, params) {
55
+ if (this.wsStore.ws?.readyState === WebSocket.CLOSED) {
56
+ logger.warn('Unable to send message in a disconnected state.');
57
+ return;
58
+ }
59
+ const messageKey = {
60
+ pluginName: this.connectionInfo.pluginName,
61
+ method,
62
+ };
63
+ const packedData = this.messageFramePacker.pack({ messageKey, payload: params });
64
+ if (!(packedData instanceof Promise)) {
65
+ this.wsStore.ws?.send(packedData);
66
+ return;
67
+ }
68
+ packedData.then((data) => {
69
+ this.wsStore.ws?.send(data);
70
+ });
71
+ }
72
+ /**
73
+ * Subscribe to a message from the other end of DevTools.
74
+ * @param method Subscribe to a message with a method name.
75
+ * @param listener Listener to be called when a message is received.
76
+ */
77
+ addMessageListener(method, listener) {
78
+ const listenersForMethod = this.listeners[method] || (this.listeners[method] = new Set());
79
+ listenersForMethod.add(listener);
80
+ return {
81
+ remove: () => {
82
+ this.listeners[method]?.delete(listener);
83
+ },
84
+ };
85
+ }
86
+ /**
87
+ * Subscribe to a message from the other end of DevTools just once.
88
+ * @param method Subscribe to a message with a method name.
89
+ * @param listener Listener to be called when a message is received.
90
+ */
91
+ addMessageListenerOnce(method, listener) {
92
+ const wrappedListenerOnce = (params) => {
93
+ listener(params);
94
+ this.listeners[method]?.delete(wrappedListenerOnce);
95
+ };
96
+ this.addMessageListener(method, wrappedListenerOnce);
97
+ }
98
+ /**
99
+ * Internal handshake message sender.
100
+ * @hidden
101
+ */
102
+ sendHandshakeMessage(params) {
103
+ if (this.wsStore.ws?.readyState === WebSocket.CLOSED) {
104
+ logger.warn('Unable to send message in a disconnected state.');
105
+ return;
106
+ }
107
+ this.wsStore.ws?.send(JSON.stringify({ ...params, __isHandshakeMessages: true }));
108
+ }
109
+ /**
110
+ * Internal handshake message listener.
111
+ * @hidden
112
+ */
113
+ addHandskakeMessageListener(listener) {
114
+ const messageListener = (event) => {
115
+ if (typeof event.data !== 'string') {
116
+ // binary data is not coming from the handshake messages.
117
+ return;
118
+ }
119
+ const data = JSON.parse(event.data);
120
+ if (!data.__isHandshakeMessages) {
121
+ return;
122
+ }
123
+ delete data.__isHandshakeMessages;
124
+ const params = data;
125
+ if (params.pluginName && params.pluginName !== this.connectionInfo.pluginName) {
126
+ return;
127
+ }
128
+ listener(params);
129
+ };
130
+ this.wsStore.ws?.addEventListener('message', messageListener);
131
+ return {
132
+ remove: () => {
133
+ this.wsStore.ws?.removeEventListener('message', messageListener);
134
+ },
135
+ };
136
+ }
137
+ /**
138
+ * Returns whether the client is connected to the server.
139
+ */
140
+ isConnected() {
141
+ return this.wsStore.ws?.readyState === WebSocket.OPEN;
142
+ }
143
+ /**
144
+ * The method to create the WebSocket connection.
145
+ */
146
+ connectAsync() {
147
+ return new Promise((resolve, reject) => {
148
+ const endpoint = 'expo-dev-plugins/broadcast';
149
+ const ws = new WebSocketWithReconnect(`ws://${this.connectionInfo.devServer}/${endpoint}`, {
150
+ binaryType: this.options?.websocketBinaryType,
151
+ onError: (e) => {
152
+ if (e instanceof Error) {
153
+ console.warn(`Error happened from the WebSocket connection: ${e.message}\n${e.stack}`);
154
+ }
155
+ else {
156
+ console.warn(`Error happened from the WebSocket connection: ${JSON.stringify(e)}`);
157
+ }
158
+ },
159
+ });
160
+ ws.addEventListener('open', () => {
161
+ resolve(ws);
162
+ });
163
+ ws.addEventListener('error', (e) => {
164
+ reject(e);
165
+ });
166
+ ws.addEventListener('close', (e) => {
167
+ logger.info('WebSocket closed', e.code, e.reason);
168
+ });
169
+ });
170
+ }
171
+ handleMessage = async (event) => {
172
+ let data;
173
+ if (typeof event.data === 'string') {
174
+ data = event.data;
175
+ }
176
+ else if (event.data instanceof ArrayBuffer) {
177
+ data = event.data;
178
+ }
179
+ else if (ArrayBuffer.isView(event.data)) {
180
+ data = event.data.buffer;
181
+ }
182
+ else if (event.data instanceof Blob) {
183
+ data = await blobToArrayBufferAsync(event.data);
184
+ }
185
+ else {
186
+ logger.warn('Unsupported received data type in handleMessageImpl');
187
+ return;
188
+ }
189
+ const { messageKey, payload, ...rest } = this.messageFramePacker.unpack(data);
190
+ // @ts-expect-error: `__isHandshakeMessages` is a private field that is not part of the MessageFramePacker type.
191
+ if (rest?.__isHandshakeMessages === true) {
192
+ return;
193
+ }
194
+ if (messageKey.pluginName && messageKey.pluginName !== this.connectionInfo.pluginName) {
195
+ return;
196
+ }
197
+ const listenersForMethod = this.listeners[messageKey.method];
198
+ if (listenersForMethod) {
199
+ for (const listener of listenersForMethod) {
200
+ listener(payload);
201
+ }
202
+ }
203
+ };
204
+ /**
205
+ * Get the WebSocket backing store. Exposed for testing.
206
+ * @hidden
207
+ */
208
+ getWebSocketBackingStore() {
209
+ return this.wsStore;
210
+ }
211
+ }
212
+ //# sourceMappingURL=DevToolsPluginClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DevToolsPluginClient.js","sourceRoot":"","sources":["../src/DevToolsPluginClient.ts"],"names":[],"mappings":"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","sourcesContent":["import { MessageFramePacker } from './MessageFramePacker';\nimport { WebSocketBackingStore } from './WebSocketBackingStore';\nimport {\n WebSocketWithReconnect,\n type WebSocketCloseEvent,\n type WebSocketMessageEvent,\n} from './WebSocketWithReconnect';\nimport type {\n ConnectionInfo,\n DevToolsPluginClientOptions,\n HandshakeMessageParams,\n} from './devtools.types';\nimport * as logger from './logger';\nimport { blobToArrayBufferAsync } from './utils/blobUtils';\n\ninterface MessageFramePackerMessageKey {\n pluginName: string;\n method: string;\n}\n\nexport interface EventSubscription {\n remove(): void;\n}\n\n/**\n * This client is for the Expo DevTools Plugins to communicate between the app and the DevTools webpage hosted in a browser.\n * All the code should be both compatible with browsers and React Native.\n */\nexport abstract class DevToolsPluginClient {\n private listeners: Record<string, undefined | Set<(params: any) => void>>;\n\n private static defaultWSStore: WebSocketBackingStore = new WebSocketBackingStore();\n private readonly wsStore: WebSocketBackingStore = DevToolsPluginClient.defaultWSStore;\n\n protected isClosed = false;\n protected retries = 0;\n private readonly messageFramePacker: MessageFramePacker<MessageFramePackerMessageKey> =\n new MessageFramePacker();\n\n public constructor(\n public readonly connectionInfo: ConnectionInfo,\n private readonly options?: DevToolsPluginClientOptions\n ) {\n this.wsStore = connectionInfo.wsStore || DevToolsPluginClient.defaultWSStore;\n this.listeners = Object.create(null);\n }\n\n /**\n * Initialize the connection.\n * @hidden\n */\n public async initAsync(): Promise<void> {\n if (this.wsStore.ws == null) {\n this.wsStore.ws = await this.connectAsync();\n }\n this.wsStore.refCount += 1;\n this.wsStore.ws.addEventListener('message', this.handleMessage);\n }\n\n /**\n * Close the connection.\n */\n public async closeAsync(): Promise<void> {\n this.isClosed = true;\n this.wsStore.ws?.removeEventListener('message', this.handleMessage);\n this.wsStore.refCount -= 1;\n if (this.wsStore.refCount < 1) {\n this.wsStore.ws?.close();\n this.wsStore.ws = null;\n }\n this.listeners = Object.create(null);\n }\n\n /**\n * Send a message to the other end of DevTools.\n * @param method A method name.\n * @param params any extra payload.\n */\n public sendMessage(method: string, params: any) {\n if (this.wsStore.ws?.readyState === WebSocket.CLOSED) {\n logger.warn('Unable to send message in a disconnected state.');\n return;\n }\n const messageKey: MessageFramePackerMessageKey = {\n pluginName: this.connectionInfo.pluginName,\n method,\n };\n const packedData = this.messageFramePacker.pack({ messageKey, payload: params });\n if (!(packedData instanceof Promise)) {\n this.wsStore.ws?.send(packedData);\n return;\n }\n packedData.then((data) => {\n this.wsStore.ws?.send(data);\n });\n }\n\n /**\n * Subscribe to a message from the other end of DevTools.\n * @param method Subscribe to a message with a method name.\n * @param listener Listener to be called when a message is received.\n */\n public addMessageListener(method: string, listener: (params: any) => void): EventSubscription {\n const listenersForMethod = this.listeners[method] || (this.listeners[method] = new Set());\n listenersForMethod.add(listener);\n return {\n remove: () => {\n this.listeners[method]?.delete(listener);\n },\n };\n }\n\n /**\n * Subscribe to a message from the other end of DevTools just once.\n * @param method Subscribe to a message with a method name.\n * @param listener Listener to be called when a message is received.\n */\n public addMessageListenerOnce(method: string, listener: (params: any) => void): void {\n const wrappedListenerOnce = (params: any): void => {\n listener(params);\n this.listeners[method]?.delete(wrappedListenerOnce);\n };\n this.addMessageListener(method, wrappedListenerOnce);\n }\n\n /**\n * Internal handshake message sender.\n * @hidden\n */\n protected sendHandshakeMessage(params: HandshakeMessageParams) {\n if (this.wsStore.ws?.readyState === WebSocket.CLOSED) {\n logger.warn('Unable to send message in a disconnected state.');\n return;\n }\n this.wsStore.ws?.send(JSON.stringify({ ...params, __isHandshakeMessages: true }));\n }\n\n /**\n * Internal handshake message listener.\n * @hidden\n */\n protected addHandskakeMessageListener(\n listener: (params: HandshakeMessageParams) => void\n ): EventSubscription {\n const messageListener = (event: MessageEvent) => {\n if (typeof event.data !== 'string') {\n // binary data is not coming from the handshake messages.\n return;\n }\n\n const data = JSON.parse(event.data);\n if (!data.__isHandshakeMessages) {\n return;\n }\n delete data.__isHandshakeMessages;\n const params = data as HandshakeMessageParams;\n if (params.pluginName && params.pluginName !== this.connectionInfo.pluginName) {\n return;\n }\n listener(params);\n };\n\n this.wsStore.ws?.addEventListener('message', messageListener);\n return {\n remove: () => {\n this.wsStore.ws?.removeEventListener('message', messageListener);\n },\n };\n }\n\n /**\n * Returns whether the client is connected to the server.\n */\n public isConnected(): boolean {\n return this.wsStore.ws?.readyState === WebSocket.OPEN;\n }\n\n /**\n * The method to create the WebSocket connection.\n */\n protected connectAsync(): Promise<WebSocket> {\n return new Promise((resolve, reject) => {\n const endpoint = 'expo-dev-plugins/broadcast';\n const ws = new WebSocketWithReconnect(`ws://${this.connectionInfo.devServer}/${endpoint}`, {\n binaryType: this.options?.websocketBinaryType,\n onError: (e: unknown) => {\n if (e instanceof Error) {\n console.warn(`Error happened from the WebSocket connection: ${e.message}\\n${e.stack}`);\n } else {\n console.warn(`Error happened from the WebSocket connection: ${JSON.stringify(e)}`);\n }\n },\n });\n ws.addEventListener('open', () => {\n resolve(ws);\n });\n ws.addEventListener('error', (e) => {\n reject(e);\n });\n ws.addEventListener('close', (e: WebSocketCloseEvent) => {\n logger.info('WebSocket closed', e.code, e.reason);\n });\n });\n }\n\n protected handleMessage = async (event: WebSocketMessageEvent) => {\n let data: ArrayBuffer | string;\n if (typeof event.data === 'string') {\n data = event.data;\n } else if (event.data instanceof ArrayBuffer) {\n data = event.data;\n } else if (ArrayBuffer.isView(event.data)) {\n data = event.data.buffer as ArrayBuffer;\n } else if (event.data instanceof Blob) {\n data = await blobToArrayBufferAsync(event.data);\n } else {\n logger.warn('Unsupported received data type in handleMessageImpl');\n return;\n }\n const { messageKey, payload, ...rest } = this.messageFramePacker.unpack(data);\n // @ts-expect-error: `__isHandshakeMessages` is a private field that is not part of the MessageFramePacker type.\n if (rest?.__isHandshakeMessages === true) {\n return;\n }\n if (messageKey.pluginName && messageKey.pluginName !== this.connectionInfo.pluginName) {\n return;\n }\n\n const listenersForMethod = this.listeners[messageKey.method];\n if (listenersForMethod) {\n for (const listener of listenersForMethod) {\n listener(payload);\n }\n }\n };\n\n /**\n * Get the WebSocket backing store. Exposed for testing.\n * @hidden\n */\n public getWebSocketBackingStore(): WebSocketBackingStore {\n return this.wsStore;\n }\n}\n"]}
@@ -0,0 +1,16 @@
1
+ import type { DevToolsPluginClient } from './DevToolsPluginClient';
2
+ import type { ConnectionInfo, DevToolsPluginClientOptions } from './devtools.types';
3
+ /**
4
+ * Factory of DevToolsPluginClient based on sender types.
5
+ * @hidden
6
+ */
7
+ export declare function createDevToolsPluginClient(connectionInfo: ConnectionInfo, options?: DevToolsPluginClientOptions): Promise<DevToolsPluginClient>;
8
+ /**
9
+ * Public API to get the DevToolsPluginClient instance.
10
+ */
11
+ export declare function getDevToolsPluginClientAsync(pluginName: string, options?: DevToolsPluginClientOptions): Promise<DevToolsPluginClient>;
12
+ /**
13
+ * Internal testing API to cleanup all DevToolsPluginClient instances.
14
+ */
15
+ export declare function cleanupDevToolsPluginInstances(): void;
16
+ //# sourceMappingURL=DevToolsPluginClientFactory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DevToolsPluginClientFactory.d.ts","sourceRoot":"","sources":["../src/DevToolsPluginClientFactory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAGnE,OAAO,KAAK,EAAE,cAAc,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAKpF;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,cAAc,EAAE,cAAc,EAC9B,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,oBAAoB,CAAC,CAS/B;AAED;;GAEG;AACH,wBAAsB,4BAA4B,CAChD,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,oBAAoB,CAAC,CAyB/B;AAED;;GAEG;AACH,wBAAgB,8BAA8B,SAU7C"}
@@ -0,0 +1,60 @@
1
+ import { DevToolsPluginClientImplApp } from './DevToolsPluginClientImplApp';
2
+ import { DevToolsPluginClientImplBrowser } from './DevToolsPluginClientImplBrowser';
3
+ import { getConnectionInfo } from './getConnectionInfo';
4
+ const instanceMap = {};
5
+ /**
6
+ * Factory of DevToolsPluginClient based on sender types.
7
+ * @hidden
8
+ */
9
+ export async function createDevToolsPluginClient(connectionInfo, options) {
10
+ let client;
11
+ if (connectionInfo.sender === 'app') {
12
+ client = new DevToolsPluginClientImplApp(connectionInfo, options);
13
+ }
14
+ else {
15
+ client = new DevToolsPluginClientImplBrowser(connectionInfo, options);
16
+ }
17
+ await client.initAsync();
18
+ return client;
19
+ }
20
+ /**
21
+ * Public API to get the DevToolsPluginClient instance.
22
+ */
23
+ export async function getDevToolsPluginClientAsync(pluginName, options) {
24
+ const connectionInfo = getConnectionInfo();
25
+ let instance = instanceMap[pluginName];
26
+ if (instance != null) {
27
+ if (instance instanceof Promise) {
28
+ return instance;
29
+ }
30
+ if (instance.isConnected() === false ||
31
+ instance.connectionInfo.devServer !== connectionInfo.devServer) {
32
+ await instance.closeAsync();
33
+ delete instanceMap[pluginName];
34
+ instance = null;
35
+ }
36
+ }
37
+ if (instance == null) {
38
+ const instancePromise = createDevToolsPluginClient({ ...connectionInfo, pluginName }, options);
39
+ instanceMap[pluginName] = instancePromise;
40
+ instance = await instancePromise;
41
+ instanceMap[pluginName] = instance;
42
+ }
43
+ return instance;
44
+ }
45
+ /**
46
+ * Internal testing API to cleanup all DevToolsPluginClient instances.
47
+ */
48
+ export function cleanupDevToolsPluginInstances() {
49
+ for (const pluginName of Object.keys(instanceMap)) {
50
+ const instance = instanceMap[pluginName];
51
+ delete instanceMap[pluginName];
52
+ if (instance instanceof Promise) {
53
+ instance.then((instance) => instance.closeAsync());
54
+ }
55
+ else {
56
+ instance.closeAsync();
57
+ }
58
+ }
59
+ }
60
+ //# sourceMappingURL=DevToolsPluginClientFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DevToolsPluginClientFactory.js","sourceRoot":"","sources":["../src/DevToolsPluginClientFactory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,+BAA+B,EAAE,MAAM,mCAAmC,CAAC;AAEpF,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,MAAM,WAAW,GAAyE,EAAE,CAAC;AAE7F;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,cAA8B,EAC9B,OAAqC;IAErC,IAAI,MAA4B,CAAC;IACjC,IAAI,cAAc,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;QACpC,MAAM,GAAG,IAAI,2BAA2B,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;IACpE,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,IAAI,+BAA+B,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;IACxE,CAAC;IACD,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC;IACzB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,UAAkB,EAClB,OAAqC;IAErC,MAAM,cAAc,GAAG,iBAAiB,EAAE,CAAC;IAE3C,IAAI,QAAQ,GACV,WAAW,CAAC,UAAU,CAAC,CAAC;IAC1B,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,QAAQ,YAAY,OAAO,EAAE,CAAC;YAChC,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,IACE,QAAQ,CAAC,WAAW,EAAE,KAAK,KAAK;YAChC,QAAQ,CAAC,cAAc,CAAC,SAAS,KAAK,cAAc,CAAC,SAAS,EAC9D,CAAC;YACD,MAAM,QAAQ,CAAC,UAAU,EAAE,CAAC;YAC5B,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC;YAC/B,QAAQ,GAAG,IAAI,CAAC;QAClB,CAAC;IACH,CAAC;IACD,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,eAAe,GAAG,0BAA0B,CAAC,EAAE,GAAG,cAAc,EAAE,UAAU,EAAE,EAAE,OAAO,CAAC,CAAC;QAC/F,WAAW,CAAC,UAAU,CAAC,GAAG,eAAe,CAAC;QAC1C,QAAQ,GAAG,MAAM,eAAe,CAAC;QACjC,WAAW,CAAC,UAAU,CAAC,GAAG,QAAQ,CAAC;IACrC,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,8BAA8B;IAC5C,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;QACzC,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC;QAC/B,IAAI,QAAQ,YAAY,OAAO,EAAE,CAAC;YAChC,QAAQ,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QACrD,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,UAAU,EAAE,CAAC;QACxB,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["import type { DevToolsPluginClient } from './DevToolsPluginClient';\nimport { DevToolsPluginClientImplApp } from './DevToolsPluginClientImplApp';\nimport { DevToolsPluginClientImplBrowser } from './DevToolsPluginClientImplBrowser';\nimport type { ConnectionInfo, DevToolsPluginClientOptions } from './devtools.types';\nimport { getConnectionInfo } from './getConnectionInfo';\n\nconst instanceMap: Record<string, DevToolsPluginClient | Promise<DevToolsPluginClient>> = {};\n\n/**\n * Factory of DevToolsPluginClient based on sender types.\n * @hidden\n */\nexport async function createDevToolsPluginClient(\n connectionInfo: ConnectionInfo,\n options?: DevToolsPluginClientOptions\n): Promise<DevToolsPluginClient> {\n let client: DevToolsPluginClient;\n if (connectionInfo.sender === 'app') {\n client = new DevToolsPluginClientImplApp(connectionInfo, options);\n } else {\n client = new DevToolsPluginClientImplBrowser(connectionInfo, options);\n }\n await client.initAsync();\n return client;\n}\n\n/**\n * Public API to get the DevToolsPluginClient instance.\n */\nexport async function getDevToolsPluginClientAsync(\n pluginName: string,\n options?: DevToolsPluginClientOptions\n): Promise<DevToolsPluginClient> {\n const connectionInfo = getConnectionInfo();\n\n let instance: DevToolsPluginClient | Promise<DevToolsPluginClient> | null =\n instanceMap[pluginName];\n if (instance != null) {\n if (instance instanceof Promise) {\n return instance;\n }\n if (\n instance.isConnected() === false ||\n instance.connectionInfo.devServer !== connectionInfo.devServer\n ) {\n await instance.closeAsync();\n delete instanceMap[pluginName];\n instance = null;\n }\n }\n if (instance == null) {\n const instancePromise = createDevToolsPluginClient({ ...connectionInfo, pluginName }, options);\n instanceMap[pluginName] = instancePromise;\n instance = await instancePromise;\n instanceMap[pluginName] = instance;\n }\n return instance;\n}\n\n/**\n * Internal testing API to cleanup all DevToolsPluginClient instances.\n */\nexport function cleanupDevToolsPluginInstances() {\n for (const pluginName of Object.keys(instanceMap)) {\n const instance = instanceMap[pluginName];\n delete instanceMap[pluginName];\n if (instance instanceof Promise) {\n instance.then((instance) => instance.closeAsync());\n } else {\n instance.closeAsync();\n }\n }\n}\n"]}
@@ -0,0 +1,15 @@
1
+ import { DevToolsPluginClient } from './DevToolsPluginClient';
2
+ /**
3
+ * The DevToolsPluginClient for the app -> browser communication.
4
+ */
5
+ export declare class DevToolsPluginClientImplApp extends DevToolsPluginClient {
6
+ private browserClientMap;
7
+ /**
8
+ * Initialize the connection.
9
+ * @hidden
10
+ */
11
+ initAsync(): Promise<void>;
12
+ private addHandshakeHandler;
13
+ private terminateBrowserClient;
14
+ }
15
+ //# sourceMappingURL=DevToolsPluginClientImplApp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DevToolsPluginClientImplApp.d.ts","sourceRoot":"","sources":["../src/DevToolsPluginClientImplApp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAG9D;;GAEG;AACH,qBAAa,2BAA4B,SAAQ,oBAAoB;IAEnE,OAAO,CAAC,gBAAgB,CAA8B;IAEtD;;;OAGG;IACY,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAKzC,OAAO,CAAC,mBAAmB;IA4B3B,OAAO,CAAC,sBAAsB;CAQ/B"}
@@ -0,0 +1,47 @@
1
+ import { DevToolsPluginClient } from './DevToolsPluginClient';
2
+ import * as logger from './logger';
3
+ /**
4
+ * The DevToolsPluginClient for the app -> browser communication.
5
+ */
6
+ export class DevToolsPluginClientImplApp extends DevToolsPluginClient {
7
+ // Map of pluginName -> browserClientId
8
+ browserClientMap = {};
9
+ /**
10
+ * Initialize the connection.
11
+ * @hidden
12
+ */
13
+ async initAsync() {
14
+ await super.initAsync();
15
+ this.addHandshakeHandler();
16
+ }
17
+ addHandshakeHandler() {
18
+ this.addHandskakeMessageListener((params) => {
19
+ if (params.method === 'handshake') {
20
+ const { pluginName, protocolVersion } = params;
21
+ // [0] Check protocol version
22
+ if (protocolVersion !== this.connectionInfo.protocolVersion) {
23
+ // Use console.warn than logger because we want to show the warning even logging is disabled.
24
+ console.warn(`Received an incompatible devtools plugin handshake message - pluginName[${pluginName}]`);
25
+ this.terminateBrowserClient(pluginName, params.browserClientId);
26
+ return;
27
+ }
28
+ // [1] Terminate duplicated browser clients for the same plugin
29
+ const previousBrowserClientId = this.browserClientMap[pluginName];
30
+ if (previousBrowserClientId != null && previousBrowserClientId !== params.browserClientId) {
31
+ logger.info(`Terminate the previous browser client connection - previousBrowserClientId[${previousBrowserClientId}]`);
32
+ this.terminateBrowserClient(pluginName, previousBrowserClientId);
33
+ }
34
+ this.browserClientMap[pluginName] = params.browserClientId;
35
+ }
36
+ });
37
+ }
38
+ terminateBrowserClient(pluginName, browserClientId) {
39
+ this.sendHandshakeMessage({
40
+ protocolVersion: this.connectionInfo.protocolVersion,
41
+ method: 'terminateBrowserClient',
42
+ browserClientId,
43
+ pluginName,
44
+ });
45
+ }
46
+ }
47
+ //# sourceMappingURL=DevToolsPluginClientImplApp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DevToolsPluginClientImplApp.js","sourceRoot":"","sources":["../src/DevToolsPluginClientImplApp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AAEnC;;GAEG;AACH,MAAM,OAAO,2BAA4B,SAAQ,oBAAoB;IACnE,uCAAuC;IAC/B,gBAAgB,GAA2B,EAAE,CAAC;IAEtD;;;OAGG;IACM,KAAK,CAAC,SAAS;QACtB,MAAM,KAAK,CAAC,SAAS,EAAE,CAAC;QACxB,IAAI,CAAC,mBAAmB,EAAE,CAAC;IAC7B,CAAC;IAEO,mBAAmB;QACzB,IAAI,CAAC,2BAA2B,CAAC,CAAC,MAAM,EAAE,EAAE;YAC1C,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;gBAClC,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,GAAG,MAAM,CAAC;gBAE/C,6BAA6B;gBAC7B,IAAI,eAAe,KAAK,IAAI,CAAC,cAAc,CAAC,eAAe,EAAE,CAAC;oBAC5D,6FAA6F;oBAC7F,OAAO,CAAC,IAAI,CACV,2EAA2E,UAAU,GAAG,CACzF,CAAC;oBACF,IAAI,CAAC,sBAAsB,CAAC,UAAU,EAAE,MAAM,CAAC,eAAe,CAAC,CAAC;oBAChE,OAAO;gBACT,CAAC;gBAED,+DAA+D;gBAC/D,MAAM,uBAAuB,GAAG,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;gBAClE,IAAI,uBAAuB,IAAI,IAAI,IAAI,uBAAuB,KAAK,MAAM,CAAC,eAAe,EAAE,CAAC;oBAC1F,MAAM,CAAC,IAAI,CACT,8EAA8E,uBAAuB,GAAG,CACzG,CAAC;oBACF,IAAI,CAAC,sBAAsB,CAAC,UAAU,EAAE,uBAAuB,CAAC,CAAC;gBACnE,CAAC;gBACD,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,GAAG,MAAM,CAAC,eAAe,CAAC;YAC7D,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,sBAAsB,CAAC,UAAkB,EAAE,eAAuB;QACxE,IAAI,CAAC,oBAAoB,CAAC;YACxB,eAAe,EAAE,IAAI,CAAC,cAAc,CAAC,eAAe;YACpD,MAAM,EAAE,wBAAwB;YAChC,eAAe;YACf,UAAU;SACX,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["import { DevToolsPluginClient } from './DevToolsPluginClient';\nimport * as logger from './logger';\n\n/**\n * The DevToolsPluginClient for the app -> browser communication.\n */\nexport class DevToolsPluginClientImplApp extends DevToolsPluginClient {\n // Map of pluginName -> browserClientId\n private browserClientMap: Record<string, string> = {};\n\n /**\n * Initialize the connection.\n * @hidden\n */\n override async initAsync(): Promise<void> {\n await super.initAsync();\n this.addHandshakeHandler();\n }\n\n private addHandshakeHandler() {\n this.addHandskakeMessageListener((params) => {\n if (params.method === 'handshake') {\n const { pluginName, protocolVersion } = params;\n\n // [0] Check protocol version\n if (protocolVersion !== this.connectionInfo.protocolVersion) {\n // Use console.warn than logger because we want to show the warning even logging is disabled.\n console.warn(\n `Received an incompatible devtools plugin handshake message - pluginName[${pluginName}]`\n );\n this.terminateBrowserClient(pluginName, params.browserClientId);\n return;\n }\n\n // [1] Terminate duplicated browser clients for the same plugin\n const previousBrowserClientId = this.browserClientMap[pluginName];\n if (previousBrowserClientId != null && previousBrowserClientId !== params.browserClientId) {\n logger.info(\n `Terminate the previous browser client connection - previousBrowserClientId[${previousBrowserClientId}]`\n );\n this.terminateBrowserClient(pluginName, previousBrowserClientId);\n }\n this.browserClientMap[pluginName] = params.browserClientId;\n }\n });\n }\n\n private terminateBrowserClient(pluginName: string, browserClientId: string) {\n this.sendHandshakeMessage({\n protocolVersion: this.connectionInfo.protocolVersion,\n method: 'terminateBrowserClient',\n browserClientId,\n pluginName,\n });\n }\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import { DevToolsPluginClient } from './DevToolsPluginClient';
2
+ /**
3
+ * The DevToolsPluginClient for the browser -> app communication.
4
+ */
5
+ export declare class DevToolsPluginClientImplBrowser extends DevToolsPluginClient {
6
+ private browserClientId;
7
+ /**
8
+ * Initialize the connection.
9
+ * @hidden
10
+ */
11
+ initAsync(): Promise<void>;
12
+ private startHandshake;
13
+ }
14
+ //# sourceMappingURL=DevToolsPluginClientImplBrowser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DevToolsPluginClientImplBrowser.d.ts","sourceRoot":"","sources":["../src/DevToolsPluginClientImplBrowser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAG9D;;GAEG;AACH,qBAAa,+BAAgC,SAAQ,oBAAoB;IACvE,OAAO,CAAC,eAAe,CAAiC;IAExD;;;OAGG;IACY,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAKzC,OAAO,CAAC,cAAc;CAmBvB"}
@@ -0,0 +1,32 @@
1
+ import { DevToolsPluginClient } from './DevToolsPluginClient';
2
+ import * as logger from './logger';
3
+ /**
4
+ * The DevToolsPluginClient for the browser -> app communication.
5
+ */
6
+ export class DevToolsPluginClientImplBrowser extends DevToolsPluginClient {
7
+ browserClientId = Date.now().toString();
8
+ /**
9
+ * Initialize the connection.
10
+ * @hidden
11
+ */
12
+ async initAsync() {
13
+ await super.initAsync();
14
+ this.startHandshake();
15
+ }
16
+ startHandshake() {
17
+ this.addHandskakeMessageListener((params) => {
18
+ if (params.method === 'terminateBrowserClient' &&
19
+ this.browserClientId === params.browserClientId) {
20
+ logger.info('Received terminateBrowserClient messages and terminate the current connection');
21
+ this.closeAsync();
22
+ }
23
+ });
24
+ this.sendHandshakeMessage({
25
+ protocolVersion: this.connectionInfo.protocolVersion,
26
+ pluginName: this.connectionInfo.pluginName,
27
+ method: 'handshake',
28
+ browserClientId: this.browserClientId,
29
+ });
30
+ }
31
+ }
32
+ //# sourceMappingURL=DevToolsPluginClientImplBrowser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DevToolsPluginClientImplBrowser.js","sourceRoot":"","sources":["../src/DevToolsPluginClientImplBrowser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,MAAM,MAAM,UAAU,CAAC;AAEnC;;GAEG;AACH,MAAM,OAAO,+BAAgC,SAAQ,oBAAoB;IAC/D,eAAe,GAAW,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;IAExD;;;OAGG;IACM,KAAK,CAAC,SAAS;QACtB,MAAM,KAAK,CAAC,SAAS,EAAE,CAAC;QACxB,IAAI,CAAC,cAAc,EAAE,CAAC;IACxB,CAAC;IAEO,cAAc;QACpB,IAAI,CAAC,2BAA2B,CAAC,CAAC,MAAM,EAAE,EAAE;YAC1C,IACE,MAAM,CAAC,MAAM,KAAK,wBAAwB;gBAC1C,IAAI,CAAC,eAAe,KAAK,MAAM,CAAC,eAAe,EAC/C,CAAC;gBACD,MAAM,CAAC,IAAI,CACT,+EAA+E,CAChF,CAAC;gBACF,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,CAAC;QACH,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,oBAAoB,CAAC;YACxB,eAAe,EAAE,IAAI,CAAC,cAAc,CAAC,eAAe;YACpD,UAAU,EAAE,IAAI,CAAC,cAAc,CAAC,UAAU;YAC1C,MAAM,EAAE,WAAW;YACnB,eAAe,EAAE,IAAI,CAAC,eAAe;SACtC,CAAC,CAAC;IACL,CAAC;CACF","sourcesContent":["import { DevToolsPluginClient } from './DevToolsPluginClient';\nimport * as logger from './logger';\n\n/**\n * The DevToolsPluginClient for the browser -> app communication.\n */\nexport class DevToolsPluginClientImplBrowser extends DevToolsPluginClient {\n private browserClientId: string = Date.now().toString();\n\n /**\n * Initialize the connection.\n * @hidden\n */\n override async initAsync(): Promise<void> {\n await super.initAsync();\n this.startHandshake();\n }\n\n private startHandshake() {\n this.addHandskakeMessageListener((params) => {\n if (\n params.method === 'terminateBrowserClient' &&\n this.browserClientId === params.browserClientId\n ) {\n logger.info(\n 'Received terminateBrowserClient messages and terminate the current connection'\n );\n this.closeAsync();\n }\n });\n this.sendHandshakeMessage({\n protocolVersion: this.connectionInfo.protocolVersion,\n pluginName: this.connectionInfo.pluginName,\n method: 'handshake',\n browserClientId: this.browserClientId,\n });\n }\n}\n"]}
@@ -0,0 +1,50 @@
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
+ type MessageKeyTypeBase = string | object;
33
+ type PayloadType = Uint8Array | string | number | null | undefined | object | ArrayBuffer | Blob;
34
+ interface MessageFrame<T extends MessageKeyTypeBase> {
35
+ messageKey: T;
36
+ payload?: PayloadType;
37
+ }
38
+ export declare class MessageFramePacker<T extends MessageKeyTypeBase> {
39
+ private textEncoder;
40
+ private textDecoder;
41
+ pack({ messageKey, payload }: MessageFrame<T>): string | Uint8Array | Promise<Uint8Array>;
42
+ unpack(packedData: string | ArrayBuffer): MessageFrame<T>;
43
+ private isFastPathPayload;
44
+ private payloadToUint8Array;
45
+ private packImpl;
46
+ private deserializePayload;
47
+ private static getPayloadTypeIndicator;
48
+ }
49
+ export {};
50
+ //# sourceMappingURL=MessageFramePacker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageFramePacker.d.ts","sourceRoot":"","sources":["../src/MessageFramePacker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAIH,KAAK,kBAAkB,GAAG,MAAM,GAAG,MAAM,CAAC;AAC1C,KAAK,WAAW,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,IAAI,CAAC;AAajG,UAAU,YAAY,CAAC,CAAC,SAAS,kBAAkB;IACjD,UAAU,EAAE,CAAC,CAAC;IACd,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED,qBAAa,kBAAkB,CAAC,CAAC,SAAS,kBAAkB;IAC1D,OAAO,CAAC,WAAW,CAAqB;IACxC,OAAO,CAAC,WAAW,CAAqB;IAEjC,IAAI,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IA2BzF,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,WAAW,GAAG,YAAY,CAAC,CAAC,CAAC;IA0BhE,OAAO,CAAC,iBAAiB;IAczB,OAAO,CAAC,mBAAmB;IAuB3B,OAAO,CAAC,QAAQ;IA8BhB,OAAO,CAAC,kBAAkB;IAoC1B,OAAO,CAAC,MAAM,CAAC,uBAAuB;CAqBvC"}