Skip to content

Commit 981d6c8

Browse files
committed
feat(websockets): split polyfill and implementation
1 parent 47e14db commit 981d6c8

File tree

2 files changed

+358
-357
lines changed

2 files changed

+358
-357
lines changed
Lines changed: 1 addition & 357 deletions
Original file line numberDiff line numberDiff line change
@@ -1,359 +1,3 @@
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';
3582

3593
global.WebSocket = WebSocket as never;

0 commit comments

Comments
 (0)