UNPKG

node-datachannel

Version:

WebRTC For Node.js and Electron. libdatachannel node bindings.

197 lines (167 loc) 6.27 kB
/* eslint-disable @typescript-eslint/no-explicit-any */ import * as exceptions from './Exception'; import { DataChannel } from '../lib/index'; export default class RTCDataChannel extends EventTarget implements globalThis.RTCDataChannel { #dataChannel: DataChannel; #readyState: RTCDataChannelState; #bufferedAmountLowThreshold: number; #binaryType: BinaryType; #maxPacketLifeTime: number | null; #maxRetransmits: number | null; #negotiated: boolean; #ordered: boolean; #closeRequested = false; // events onbufferedamountlow: ((this: RTCDataChannel, ev: Event) => any) | null; onclose: ((this: RTCDataChannel, ev: Event) => any) | null; onclosing: ((this: RTCDataChannel, ev: Event) => any) | null; onerror: ((this: RTCDataChannel, ev: Event) => any) | null; onmessage: ((this: RTCDataChannel, ev: MessageEvent) => any) | null; onopen: ((this: RTCDataChannel, ev: Event) => any) | null; constructor(dataChannel: DataChannel, opts: globalThis.RTCDataChannelInit = {}) { super(); this.#dataChannel = dataChannel; this.#binaryType = 'blob'; this.#readyState = this.#dataChannel.isOpen() ? 'open' : 'connecting'; this.#bufferedAmountLowThreshold = 0; this.#maxPacketLifeTime = opts.maxPacketLifeTime || null; this.#maxRetransmits = opts.maxRetransmits || null; this.#negotiated = opts.negotiated || false; this.#ordered = opts.ordered || true; // forward dataChannel events this.#dataChannel.onOpen(() => { this.#readyState = 'open'; this.dispatchEvent(new Event('open', {})); }); this.#dataChannel.onClosed(() => { // Simulate closing event if (!this.#closeRequested) { this.#readyState = 'closing'; this.dispatchEvent(new Event('closing')); } setImmediate(() => { this.#readyState = 'closed'; this.dispatchEvent(new Event('close')); }); }); this.#dataChannel.onError((msg) => { this.dispatchEvent( new globalThis.RTCErrorEvent('error', { error: new RTCError( { errorDetail: 'data-channel-failure', }, msg, ), }), ); }); this.#dataChannel.onBufferedAmountLow(() => { this.dispatchEvent(new Event('bufferedamountlow')); }); this.#dataChannel.onMessage((data) => { if (ArrayBuffer.isView(data)) { if (this.binaryType == 'arraybuffer') data = data.buffer; else data = Buffer.from(data.buffer); } this.dispatchEvent(new MessageEvent('message', { data })); }); // forward events to properties this.addEventListener('message', (e) => { if (this.onmessage) this.onmessage(e as MessageEvent); }); this.addEventListener('bufferedamountlow', (e) => { if (this.onbufferedamountlow) this.onbufferedamountlow(e); }); this.addEventListener('error', (e) => { if (this.onerror) this.onerror(e); }); this.addEventListener('close', (e) => { if (this.onclose) this.onclose(e); }); this.addEventListener('closing', (e) => { if (this.onclosing) this.onclosing(e); }); this.addEventListener('open', (e) => { if (this.onopen) this.onopen(e); }); } set binaryType(type) { if (type !== 'blob' && type !== 'arraybuffer') { throw new DOMException( "Failed to set the 'binaryType' property on 'RTCDataChannel': Unknown binary type : " + type, 'TypeMismatchError', ); } this.#binaryType = type; } get binaryType(): BinaryType { return this.#binaryType; } get bufferedAmount(): number { return this.#dataChannel.bufferedAmount(); } get bufferedAmountLowThreshold(): number { return this.#bufferedAmountLowThreshold; } set bufferedAmountLowThreshold(value) { const number = Number(value) || 0; this.#bufferedAmountLowThreshold = number; this.#dataChannel.setBufferedAmountLowThreshold(number); } get id(): number | null { return this.#dataChannel.getId(); } get label(): string { return this.#dataChannel.getLabel(); } get maxPacketLifeTime(): number | null { return this.#maxPacketLifeTime; } get maxRetransmits(): number | null { return this.#maxRetransmits; } get negotiated(): boolean { return this.#negotiated; } get ordered(): boolean { return this.#ordered; } get protocol(): string { return this.#dataChannel.getProtocol(); } get readyState(): globalThis.RTCDataChannelState { return this.#readyState; } send(data): void { if (this.#readyState !== 'open') { throw new exceptions.InvalidStateError( "Failed to execute 'send' on 'RTCDataChannel': RTCDataChannel.readyState is not 'open'", ); } // Needs network error, type error implemented if (typeof data === 'string') { this.#dataChannel.sendMessage(data); } else if (data instanceof Blob) { data.arrayBuffer().then((ab) => { if (process?.versions?.bun) { this.#dataChannel.sendMessageBinary(Buffer.from(ab)); } else { this.#dataChannel.sendMessageBinary(new Uint8Array(ab)); } }); } else { if (process?.versions?.bun) { this.#dataChannel.sendMessageBinary(Buffer.from(data)); } else { this.#dataChannel.sendMessageBinary(new Uint8Array(data)); } } } close(): void { this.#closeRequested = true; this.#dataChannel.close(); } }