|
1 | | -/** |
2 | | - * Copyright (c) Meta Platforms, Inc. and affiliates. |
3 | | - * |
4 | | - * This source code is licensed under the MIT license found in the |
5 | | - * LICENSE file in the root directory of this source tree. |
6 | | - * |
7 | | - * @format |
8 | | - * @flow |
9 | | - */ |
10 | | - |
11 | | -import { NativeBridge } from './bridge'; |
12 | | -import { WebSocketEvent } from './common'; |
13 | | -import { WebSocketPolyfill } from './websocket.definition'; |
14 | | - |
15 | | -type ArrayBufferView = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | DataView; |
16 | | - |
17 | | -type BinaryType = 'blob' | 'arraybuffer'; |
18 | | - |
19 | | -const CONNECTING = 0; |
20 | | -const OPEN = 1; |
21 | | -const CLOSING = 2; |
22 | | -const CLOSED = 3; |
23 | | - |
24 | | -const CLOSE_NORMAL = 1000; |
25 | | - |
26 | | -// const WEBSOCKET_EVENTS = ['close', 'error', 'message', 'open']; |
27 | | - |
28 | | -// type WebSocketEventDefinitions = { |
29 | | -// websocketOpen: [{ id: number; protocol: string }]; |
30 | | -// websocketClosed: [{ id: number; code: number; reason: string }]; |
31 | | -// websocketMessage: [ |
32 | | -// { type: 'binary'; id: number; data: string } | { type: 'text'; id: number; data: string } |
33 | | -// // | {type: 'blob', id: number, data: BlobData}, |
34 | | -// ]; |
35 | | -// websocketFailed: [{ id: number; message: string }]; |
36 | | -// }; |
37 | | - |
38 | | -/** |
39 | | - * Browser-compatible WebSockets implementation. |
40 | | - * |
41 | | - * See https://developer.mozilla.org/en-US/docs/Web/API/WebSocket |
42 | | - * See https://github.com/websockets/ws |
43 | | - */ |
44 | | -class WebSocket implements WebSocketPolyfill { |
45 | | - private nativeBridge: NativeBridge; |
46 | | - static CONNECTING: number = CONNECTING; |
47 | | - static OPEN: number = OPEN; |
48 | | - static CLOSING: number = CLOSING; |
49 | | - static CLOSED: number = CLOSED; |
50 | | - static HANDLE_THREADING = true; |
51 | | - |
52 | | - CONNECTING: number = CONNECTING; |
53 | | - OPEN: number = OPEN; |
54 | | - CLOSING: number = CLOSING; |
55 | | - CLOSED: number = CLOSED; |
56 | | - |
57 | | - // _socketId: number; |
58 | | - // _eventEmitter: NativeEventEmitter<WebSocketEventDefinitions>; |
59 | | - // _subscriptions: Array<EventSubscription>; |
60 | | - _binaryType: BinaryType; |
61 | | - |
62 | | - /* eslint-disable @typescript-eslint/ban-types */ |
63 | | - onclose?: Function; |
64 | | - onerror?: Function; |
65 | | - onmessage?: Function; |
66 | | - onopen?: Function; |
67 | | - /* eslint-enable @typescript-eslint/ban-types */ |
68 | | - |
69 | | - bufferedAmount?: number; |
70 | | - extension?: string; |
71 | | - protocol?: string; |
72 | | - readyState: number = CONNECTING; |
73 | | - url: string; |
74 | | - |
75 | | - private listeners: Record<string, Map<EventListenerOrEventListenerObject, boolean | AddEventListenerOptions> | undefined> = { |
76 | | - open: new Map(), |
77 | | - close: new Map(), |
78 | | - error: new Map(), |
79 | | - message: new Map(), |
80 | | - }; |
81 | | - |
82 | | - constructor(url: string, protocols?: string | Array<string>, options?: { headers: { origin?: string; [key: string]: unknown }; nativescript: { handleThreading: boolean }; [key: string]: unknown }) { |
83 | | - this.nativeBridge = new NativeBridge(this); |
84 | | - this.url = url; |
85 | | - if (typeof protocols === 'string') { |
86 | | - protocols = [protocols]; |
87 | | - } |
88 | | - if (!Array.isArray(protocols)) { |
89 | | - protocols = []; |
90 | | - } |
91 | | - |
92 | | - const { headers = {}, nativescript = { handleThreading: true }, ...unrecognized } = options || ({} as { headers: { origin?: string; [key: string]: unknown }; nativescript: { handleThreading: boolean }; [key: string]: unknown }); |
93 | | - this.nativeBridge.handleThreading = nativescript?.handleThreading ?? WebSocket.HANDLE_THREADING; |
94 | | - this._binaryType = 'arraybuffer'; |
95 | | - |
96 | | - // Preserve deprecated backwards compatibility for the 'origin' option |
97 | | - if (unrecognized && typeof unrecognized.origin === 'string') { |
98 | | - console.warn('Specifying `origin` as a WebSocket connection option is deprecated. Include it under `headers` instead.'); |
99 | | - /* $FlowFixMe[prop-missing] (>=0.54.0 site=react_native_fb,react_native_ |
100 | | - * oss) This comment suppresses an error found when Flow v0.54 was |
101 | | - * deployed. To see the error delete this comment and run Flow. */ |
102 | | - headers.origin = unrecognized.origin; |
103 | | - /* $FlowFixMe[prop-missing] (>=0.54.0 site=react_native_fb,react_native_ |
104 | | - * oss) This comment suppresses an error found when Flow v0.54 was |
105 | | - * deployed. To see the error delete this comment and run Flow. */ |
106 | | - delete unrecognized.origin; |
107 | | - } |
108 | | - |
109 | | - // Warn about and discard anything else |
110 | | - if (Object.keys(unrecognized).length > 0) { |
111 | | - console.warn('Unrecognized WebSocket connection option(s) `' + Object.keys(unrecognized).join('`, `') + '`. ' + 'Did you mean to put these under `headers`?'); |
112 | | - } |
113 | | - |
114 | | - // this._eventEmitter = new NativeEventEmitter( |
115 | | - // // T88715063: NativeEventEmitter only used this parameter on iOS. Now it uses it on all platforms, so this code was modified automatically to preserve its behavior |
116 | | - // // If you want to use the native module on other platforms, please remove this condition and test its behavior |
117 | | - // Platform.OS !== 'ios' ? null : NativeWebSocketModule, |
118 | | - // ); |
119 | | - this._registerEvents(); |
120 | | - this.nativeBridge.connect(url, protocols, { headers }); |
121 | | - // NativeWebSocketModule.connect(url, protocols, {headers}, this._socketId); |
122 | | - } |
123 | | - addEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions): void { |
124 | | - if (!callback) { |
125 | | - return; |
126 | | - } |
127 | | - this.listeners[type]?.set(callback, options ?? false); |
128 | | - } |
129 | | - // eslint-disable-next-line @typescript-eslint/no-unused-vars |
130 | | - removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions): void { |
131 | | - if (!callback) { |
132 | | - return; |
133 | | - } |
134 | | - if (this.listeners[type]?.has(callback)) { |
135 | | - this.listeners[type]?.delete(callback); |
136 | | - } |
137 | | - } |
138 | | - |
139 | | - get binaryType(): BinaryType { |
140 | | - return this._binaryType; |
141 | | - } |
142 | | - |
143 | | - set binaryType(binaryType: BinaryType) { |
144 | | - if (binaryType !== 'blob' && binaryType !== 'arraybuffer') { |
145 | | - throw new Error("binaryType must be either 'blob' or 'arraybuffer'"); |
146 | | - } |
147 | | - // if (this._binaryType === 'blob' || binaryType === 'blob') { |
148 | | - // invariant( |
149 | | - // BlobManager.isAvailable, |
150 | | - // 'Native module BlobModule is required for blob support', |
151 | | - // ); |
152 | | - // if (binaryType === 'blob') { |
153 | | - // BlobManager.addWebSocketHandler(this._socketId); |
154 | | - // } else { |
155 | | - // BlobManager.removeWebSocketHandler(this._socketId); |
156 | | - // } |
157 | | - // } |
158 | | - this._binaryType = binaryType; |
159 | | - } |
160 | | - |
161 | | - close(code?: number, reason?: string): void { |
162 | | - if (this.readyState === this.CLOSING || this.readyState === this.CLOSED) { |
163 | | - return; |
164 | | - } |
165 | | - |
166 | | - this.readyState = this.CLOSING; |
167 | | - this._close(code, reason); |
168 | | - } |
169 | | - |
170 | | - send(data: string | ArrayBuffer | ArrayBufferView | Blob): void { |
171 | | - if (this.readyState === this.CONNECTING) { |
172 | | - throw new Error('INVALID_STATE_ERR'); |
173 | | - } |
174 | | - |
175 | | - // if (data instanceof Blob) { |
176 | | - // invariant( |
177 | | - // BlobManager.isAvailable, |
178 | | - // 'Native module BlobModule is required for blob support', |
179 | | - // ); |
180 | | - // BlobManager.sendOverSocket(data, this._socketId); |
181 | | - // return; |
182 | | - // } |
183 | | - |
184 | | - if (typeof data === 'string') { |
185 | | - this.nativeBridge.send(data); |
186 | | - // NativeWebSocketModule.send(data, this._socketId); |
187 | | - return; |
188 | | - } |
189 | | - |
190 | | - if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) { |
191 | | - this.nativeBridge.send(data); |
192 | | - // NativeWebSocketModule.sendBinary(binaryToBase64(data), this._socketId); |
193 | | - return; |
194 | | - } |
195 | | - |
196 | | - throw new Error('Unsupported data type'); |
197 | | - } |
198 | | - |
199 | | - ping(): void { |
200 | | - if (this.readyState === this.CONNECTING) { |
201 | | - throw new Error('INVALID_STATE_ERR'); |
202 | | - } |
203 | | - |
204 | | - // NativeWebSocketModule.ping(this._socketId); |
205 | | - this.nativeBridge.sendPing(); |
206 | | - } |
207 | | - |
208 | | - dispatchEvent(event: WebSocketEvent) { |
209 | | - switch (event.type) { |
210 | | - case 'open': |
211 | | - this.onopen?.(event); |
212 | | - break; |
213 | | - case 'close': |
214 | | - this.onclose?.(event); |
215 | | - break; |
216 | | - case 'error': |
217 | | - this.onerror?.(event); |
218 | | - break; |
219 | | - case 'message': |
220 | | - this.onmessage?.(event); |
221 | | - break; |
222 | | - } |
223 | | - this.listeners[event.type]?.forEach((options, listener) => { |
224 | | - try { |
225 | | - if (typeof listener === 'function') { |
226 | | - listener(event as Event); |
227 | | - } else { |
228 | | - listener.handleEvent(event as Event); |
229 | | - } |
230 | | - if (typeof options !== 'boolean' && options.once) { |
231 | | - if (this.listeners[event.type]?.has(listener)) { |
232 | | - this.listeners[event.type]?.delete(listener); |
233 | | - } |
234 | | - } |
235 | | - } catch (e) { |
236 | | - // don't break events if listener throws |
237 | | - setTimeout(() => { |
238 | | - throw e; |
239 | | - }); |
240 | | - } |
241 | | - }); |
242 | | - } |
243 | | - |
244 | | - _close(code?: number, reason?: string): void { |
245 | | - // See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent |
246 | | - const statusCode = typeof code === 'number' ? code : CLOSE_NORMAL; |
247 | | - const closeReason = typeof reason === 'string' ? reason : ''; |
248 | | - this.nativeBridge.closeWithCodeReason(statusCode, closeReason); |
249 | | - // NativeWebSocketModule.close(statusCode, closeReason, this._socketId); |
250 | | - |
251 | | - // if (BlobManager.isAvailable && this._binaryType === 'blob') { |
252 | | - // BlobManager.removeWebSocketHandler(this._socketId); |
253 | | - // } |
254 | | - } |
255 | | - |
256 | | - _unregisterEvents(): void { |
257 | | - // this._subscriptions.forEach(e => e.remove()); |
258 | | - // this._subscriptions = []; |
259 | | - } |
260 | | - |
261 | | - _websocketOpen(protocol: string) { |
262 | | - this.readyState = this.OPEN; |
263 | | - this.protocol = protocol; |
264 | | - this.dispatchEvent(new WebSocketEvent('open')); |
265 | | - } |
266 | | - _websocketClosed(code: number, reason: string, wasClean: boolean) { |
267 | | - this.readyState = this.CLOSED; |
268 | | - this.dispatchEvent( |
269 | | - new WebSocketEvent('close', { |
270 | | - code: code, |
271 | | - reason: reason, |
272 | | - wasClean, |
273 | | - }) |
274 | | - ); |
275 | | - } |
276 | | - _websocketMessage(message: string | ArrayBuffer | ArrayBufferView | Blob) { |
277 | | - this.dispatchEvent(new WebSocketEvent('message', { data: message })); |
278 | | - } |
279 | | - // _websocketPong(pongPayload: NSData) {} |
280 | | - _websocketFailed(error: string) { |
281 | | - this.readyState = this.CLOSED; |
282 | | - this.dispatchEvent( |
283 | | - new WebSocketEvent('error', { |
284 | | - message: error, |
285 | | - }) |
286 | | - ); |
287 | | - this.dispatchEvent( |
288 | | - new WebSocketEvent('close', { |
289 | | - message: error, |
290 | | - }) |
291 | | - ); |
292 | | - this.close(); |
293 | | - // this._unregisterEvents(); |
294 | | - // this.close(); |
295 | | - // }), |
296 | | - } |
297 | | - |
298 | | - _registerEvents(): void { |
299 | | - // this._subscriptions = [ |
300 | | - // this._eventEmitter.addListener('websocketMessage', ev => { |
301 | | - // if (ev.id !== this._socketId) { |
302 | | - // return; |
303 | | - // } |
304 | | - // let data = ev.data; |
305 | | - // switch (ev.type) { |
306 | | - // case 'binary': |
307 | | - // data = base64.toByteArray(ev.data).buffer; |
308 | | - // break; |
309 | | - // case 'blob': |
310 | | - // data = BlobManager.createFromOptions(ev.data); |
311 | | - // break; |
312 | | - // } |
313 | | - // this.dispatchEvent(new WebSocketEvent('message', {data})); |
314 | | - // }), |
315 | | - // this._eventEmitter.addListener('websocketOpen', ev => { |
316 | | - // if (ev.id !== this._socketId) { |
317 | | - // return; |
318 | | - // } |
319 | | - // this.readyState = this.OPEN; |
320 | | - // this.protocol = ev.protocol; |
321 | | - // this.dispatchEvent(new WebSocketEvent('open')); |
322 | | - // }), |
323 | | - // this._eventEmitter.addListener('websocketClosed', ev => { |
324 | | - // if (ev.id !== this._socketId) { |
325 | | - // return; |
326 | | - // } |
327 | | - // this.readyState = this.CLOSED; |
328 | | - // this.dispatchEvent( |
329 | | - // new WebSocketEvent('close', { |
330 | | - // code: ev.code, |
331 | | - // reason: ev.reason, |
332 | | - // }), |
333 | | - // ); |
334 | | - // this._unregisterEvents(); |
335 | | - // this.close(); |
336 | | - // }), |
337 | | - // this._eventEmitter.addListener('websocketFailed', ev => { |
338 | | - // if (ev.id !== this._socketId) { |
339 | | - // return; |
340 | | - // } |
341 | | - // this.readyState = this.CLOSED; |
342 | | - // this.dispatchEvent( |
343 | | - // new WebSocketEvent('error', { |
344 | | - // message: ev.message, |
345 | | - // }), |
346 | | - // ); |
347 | | - // this.dispatchEvent( |
348 | | - // new WebSocketEvent('close', { |
349 | | - // message: ev.message, |
350 | | - // }), |
351 | | - // ); |
352 | | - // this._unregisterEvents(); |
353 | | - // this.close(); |
354 | | - // }), |
355 | | - // ]; |
356 | | - } |
357 | | -} |
| 1 | +import { WebSocket } from './websocket'; |
358 | 2 |
|
359 | 3 | global.WebSocket = WebSocket as never; |
0 commit comments