diff --git a/src/polyfill/RTCDataChannel.ts b/src/polyfill/RTCDataChannel.ts index 4300361..b4d2ec3 100644 --- a/src/polyfill/RTCDataChannel.ts +++ b/src/polyfill/RTCDataChannel.ts @@ -48,15 +48,19 @@ export default class RTCDataChannel extends EventTarget implements globalThis.RT }); this.#dataChannel.onClosed(() => { - // Simulate closing event + if (this.#readyState === 'closed') return; + if (!this.#closeRequested) { + // if close was not requested, we emit 'closing' before 'close' to match the spec behavior this.#readyState = 'closing'; this.dispatchEvent(new Event('closing')); } setImmediate(() => { - this.#readyState = 'closed'; - this.dispatchEvent(new Event('close')); + if (this.#readyState !== 'closed') { + this.#readyState = 'closed'; + this.dispatchEvent(new Event('close')); + } }); }); @@ -79,32 +83,22 @@ export default class RTCDataChannel extends EventTarget implements globalThis.RT this.#dataChannel.onMessage((data) => { if (ArrayBuffer.isView(data)) { - if (this.binaryType == 'arraybuffer') data = data.buffer; - else data = Buffer.from(data.buffer); + data = + this.binaryType === 'arraybuffer' + ? (data.buffer as ArrayBuffer) + : 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 as RTCErrorEvent); - }); - 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); - }); + this.addEventListener('open', (e) => this.onopen?.(e)); + this.addEventListener('message', (e) => this.onmessage?.(e as MessageEvent)); + this.addEventListener('error', (e) => this.onerror?.(e as RTCErrorEvent)); + this.addEventListener('close', (e) => this.onclose?.(e)); + this.addEventListener('closing', (e) => this.onclosing?.(e)); + this.addEventListener('bufferedamountlow', (e) => this.onbufferedamountlow?.(e)); } set binaryType(type) { @@ -168,7 +162,7 @@ export default class RTCDataChannel extends EventTarget implements globalThis.RT return this.#readyState; } - send(data): void { + send(data: string | Blob | ArrayBuffer | ArrayBufferView): void { if (this.#readyState !== 'open') { throw new exceptions.InvalidStateError( "Failed to execute 'send' on 'RTCDataChannel': RTCDataChannel.readyState is not 'open'", @@ -190,15 +184,19 @@ export default class RTCDataChannel extends EventTarget implements globalThis.RT this.#dataChannel.sendMessageBinary(data); } else { if (process?.versions?.bun) { - this.#dataChannel.sendMessageBinary(Buffer.from(data)); + this.#dataChannel.sendMessageBinary(Buffer.from(data as ArrayBuffer)); } else { - this.#dataChannel.sendMessageBinary(new Uint8Array(data)); + this.#dataChannel.sendMessageBinary(new Uint8Array(data as ArrayBuffer)); } } } close(): void { + if (this.#readyState === 'closing' || this.#readyState === 'closed') return; + this.#closeRequested = true; + this.#readyState = 'closing'; + setImmediate(() => { this.#dataChannel.close(); }); diff --git a/src/polyfill/RTCPeerConnection.ts b/src/polyfill/RTCPeerConnection.ts index d71ade6..d2fdb66 100644 --- a/src/polyfill/RTCPeerConnection.ts +++ b/src/polyfill/RTCPeerConnection.ts @@ -354,6 +354,17 @@ export default class RTCPeerConnection extends EventTarget implements globalThis } close(): void { + for (const dc of this.#dataChannels) { + if (dc.readyState !== 'closed' && dc.readyState !== 'closing') { + Object.defineProperty(dc, 'readyState', { + value: 'closed', + writable: false, + enumerable: true, + configurable: true, + }); + } + } + this.#peerConnection.close(); }