UNPKG

react-native-fast-ws

Version:

A modern WebSocket implementation for React Native, built with Nitro

233 lines (191 loc) 5.28 kB
import { Event, EventTarget, getEventAttributeValue, setEventAttributeValue, } from 'event-target-shim' import { NitroModules } from 'react-native-nitro-modules' import { Blob } from './blob' import { WebSocket as HybridWebSocket, WebSocketManager } from './spec.nitro' const manager = NitroModules.createHybridObject<WebSocketManager>('WebSocketManager') enum WebSocketReadyState { CONNECTING = 0, OPEN = 1, CLOSING = 2, CLOSED = 3, } /** * https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.5 */ const ABNORMAL_CLOSURE = 1006 /** * Events */ export type OpenEvent = Event export class MessageEvent extends Event { readonly data: string | Blob | ArrayBuffer constructor(data: string | Blob | ArrayBuffer) { super('message') this.data = data } } export class ErrorEvent extends Event { readonly error: string constructor(error: string) { super('error') this.error = error } } export class CloseEvent extends Event { readonly code: number readonly reason: string constructor(code: number = 0, reason: string = '') { super('close') this.code = code this.reason = reason } get wasClean() { throw new Error('Not implemented') } } /** * https://websockets.spec.whatwg.org/#interface-definition */ export class WebSocket extends EventTarget implements EventTarget<{ open: OpenEvent message: MessageEvent error: ErrorEvent close: CloseEvent }> { readonly CONNECTING = WebSocketReadyState.CONNECTING readonly OPEN = WebSocketReadyState.OPEN readonly CLOSING = WebSocketReadyState.CLOSING readonly CLOSED = WebSocketReadyState.CLOSED readonly url: string binaryType: 'arraybuffer' | 'blob' = 'blob' private _readyState: WebSocketReadyState = WebSocketReadyState.CONNECTING get readyState() { return this._readyState } get bufferedAmount() { throw new Error('Not implemented') } get extensions() { throw new Error('Not implemented') } private _protocol = '' get protocol() { return this._protocol } private readonly ws: HybridWebSocket constructor(url: string, protocols: string | string[] = []) { super() this.url = url this.ws = manager.create(url, Array.isArray(protocols) ? protocols : [protocols]) this.ws.onOpen((protocol) => { this._readyState = WebSocketReadyState.OPEN this._protocol = protocol this.dispatchEvent(new Event('open')) }) this.ws.onMessage((data) => { this.dispatchEvent(new MessageEvent(data)) }) this.ws.onArrayBuffer((buffer) => { if (this.binaryType === 'blob') { this.dispatchEvent(new MessageEvent(new Blob([buffer]))) return } this.dispatchEvent(new MessageEvent(buffer)) }) this.ws.onError((message) => { this.dispatchEvent(new ErrorEvent(message)) /** * Sending `close` frame before proceeding to close the connection * https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.7 */ this._readyState = WebSocketReadyState.CLOSED this.dispatchEvent(new CloseEvent(ABNORMAL_CLOSURE)) this.close() }) this.ws.onClose((code, reason) => { this._readyState = WebSocketReadyState.CLOSED this.dispatchEvent(new CloseEvent(code, reason)) }) this.ws.connect() } /** * https://websockets.spec.whatwg.org/#dom-websocket-send */ send(message: string | ArrayBuffer | ArrayBufferView | Blob) { if (this._readyState === WebSocketReadyState.CONNECTING) { throw new Error('InvalidStateError') } if (typeof message === 'string') { this.ws.send(message) return } if (message instanceof ArrayBuffer) { this.ws.sendArrayBuffer(message) return } if (ArrayBuffer.isView(message)) { this.ws.sendArrayBuffer(message.buffer) return } if (message instanceof Blob) { ;(async () => { const arrayBuffer = await message.arrayBuffer() this.ws.sendArrayBuffer(arrayBuffer) })() return } throw new TypeError('Invalid message type') } /** * https://websockets.spec.whatwg.org/#dom-websocket-close */ close(code: number = 1000, reason: string = '') { if ( this._readyState === WebSocketReadyState.CLOSING || this._readyState === WebSocketReadyState.CLOSED ) { return } if (code !== 1000 && (code < 3000 || code > 4999)) { throw new Error('Invalid close code. Must be 1000 or in range 3000-4999.') } this._readyState = WebSocketReadyState.CLOSING this.ws.close(code, reason) } ping() { this.ws.ping() } get onopen() { return getEventAttributeValue(this, 'open') } set onopen(value) { setEventAttributeValue(this, 'open', value) } get onmessage() { return getEventAttributeValue(this, 'message') } set onmessage(value) { setEventAttributeValue(this, 'message', value) } get onerror() { return getEventAttributeValue(this, 'error') } set onerror(value) { setEventAttributeValue(this, 'error', value) } get onclose() { return getEventAttributeValue(this, 'close') } set onclose(value) { setEventAttributeValue(this, 'close', value) } }