From 3763cf47da9e3d55ae330491a2055bbd3a1ee3b2 Mon Sep 17 00:00:00 2001 From: Murat Dogan Date: Sat, 24 May 2025 21:26:38 +0200 Subject: [PATCH 1/2] Remove extrafunctions and add webrtc package --- src/polyfill/Events.ts | 59 +++++++++++++++++++--- src/polyfill/RTCCertificate.ts | 8 ++- src/polyfill/RTCDataChannel.ts | 5 +- src/polyfill/RTCDtlsTransport.ts | 8 +-- src/polyfill/RTCError.ts | 6 +++ src/polyfill/RTCIceTransport.ts | 37 +++++++++----- src/polyfill/RTCPeerConnection.ts | 73 +++++++++++++++++---------- src/polyfill/RTCSctpTransport.ts | 14 +++-- src/polyfill/RTCSessionDescription.ts | 13 ++++- 9 files changed, 158 insertions(+), 65 deletions(-) diff --git a/src/polyfill/Events.ts b/src/polyfill/Events.ts index 53b50ae..50122fd 100644 --- a/src/polyfill/Events.ts +++ b/src/polyfill/Events.ts @@ -1,9 +1,7 @@ import RTCDataChannel from './RTCDataChannel'; +import RTCError from './RTCError'; -export class RTCPeerConnectionIceEvent - extends Event - implements globalThis.RTCPeerConnectionIceEvent -{ +export class RTCPeerConnectionIceEvent extends Event implements globalThis.RTCPeerConnectionIceEvent { #candidate: globalThis.RTCIceCandidate; constructor(candidate: globalThis.RTCIceCandidate) { @@ -15,20 +13,67 @@ export class RTCPeerConnectionIceEvent get candidate(): globalThis.RTCIceCandidate { return this.#candidate; } + + get url(): string { + return ''; + } } export class RTCDataChannelEvent extends Event implements globalThis.RTCDataChannelEvent { #channel: RTCDataChannel; - constructor(type: string, eventInitDict: globalThis.RTCDataChannelEventInit) { + constructor(type: string = 'datachannel', eventInitDict: globalThis.RTCDataChannelEventInit) { super(type); - if (type && !eventInitDict.channel) throw new TypeError('channel member is required'); + if (arguments.length === 0) + throw new TypeError( + `Failed to construct 'RTCDataChannelEvent': 2 arguments required, but only ${arguments.length} present.`, + ); + if (typeof eventInitDict !== 'object') + throw new TypeError( + "Failed to construct 'RTCDataChannelEvent': The provided value is not of type 'RTCDataChannelEventInit'.", + ); + if (!eventInitDict.channel) + throw new TypeError( + "Failed to construct 'RTCDataChannelEvent': Failed to read the 'channel' property from 'RTCDataChannelEventInit': Required member is undefined.", + ); + if (eventInitDict.channel.constructor !== RTCDataChannel) + throw new TypeError( + "Failed to construct 'RTCDataChannelEvent': Failed to read the 'channel' property from 'RTCDataChannelEventInit': Failed to convert value to 'RTCDataChannel'.", + ); - this.#channel = eventInitDict?.channel as RTCDataChannel; + this.#channel = eventInitDict?.channel; } get channel(): RTCDataChannel { return this.#channel; } } + +export class RTCErrorEvent extends Event implements globalThis.RTCErrorEvent { + #error: RTCError; + constructor(type: string, init: globalThis.RTCErrorEventInit) { + if (arguments.length < 2) + throw new TypeError( + `Failed to construct 'RTCErrorEvent': 2 arguments required, but only ${arguments.length} present.`, + ); + if (typeof init !== 'object') + throw new TypeError( + "Failed to construct 'RTCErrorEvent': The provided value is not of type 'RTCErrorEventInit'.", + ); + if (!init.error) + throw new TypeError( + "Failed to construct 'RTCErrorEvent': Failed to read the 'error' property from 'RTCErrorEventInit': Required member is undefined.", + ); + if (init.error.constructor !== RTCError) + throw new TypeError( + "Failed to construct 'RTCErrorEvent': Failed to read the 'error' property from 'RTCErrorEventInit': Failed to convert value to 'RTCError'.", + ); + super(type || 'error'); + this.#error = init.error; + } + + get error(): RTCError { + return this.#error; + } +} diff --git a/src/polyfill/RTCCertificate.ts b/src/polyfill/RTCCertificate.ts index df09a31..7562762 100644 --- a/src/polyfill/RTCCertificate.ts +++ b/src/polyfill/RTCCertificate.ts @@ -1,6 +1,6 @@ export default class RTCCertificate implements globalThis.RTCCertificate { - #expires: number; - #fingerprints: globalThis.RTCDtlsFingerprint[]; + #expires: number = 0; + #fingerprints: globalThis.RTCDtlsFingerprint[] = []; constructor() { this.#expires = null; @@ -14,4 +14,8 @@ export default class RTCCertificate implements globalThis.RTCCertificate { getFingerprints(): globalThis.RTCDtlsFingerprint[] { return this.#fingerprints; } + + getAlgorithm(): string { + return ''; + } } diff --git a/src/polyfill/RTCDataChannel.ts b/src/polyfill/RTCDataChannel.ts index cfbaf4e..4300361 100644 --- a/src/polyfill/RTCDataChannel.ts +++ b/src/polyfill/RTCDataChannel.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as exceptions from './Exception'; import { DataChannel } from '../lib/index'; +import { RTCErrorEvent } from './Events'; export default class RTCDataChannel extends EventTarget implements globalThis.RTCDataChannel { #dataChannel: DataChannel; @@ -61,7 +62,7 @@ export default class RTCDataChannel extends EventTarget implements globalThis.RT this.#dataChannel.onError((msg) => { this.dispatchEvent( - new globalThis.RTCErrorEvent('error', { + new RTCErrorEvent('error', { error: new RTCError( { errorDetail: 'data-channel-failure', @@ -93,7 +94,7 @@ export default class RTCDataChannel extends EventTarget implements globalThis.RT if (this.onbufferedamountlow) this.onbufferedamountlow(e); }); this.addEventListener('error', (e) => { - if (this.onerror) this.onerror(e); + if (this.onerror) this.onerror(e as RTCErrorEvent); }); this.addEventListener('close', (e) => { if (this.onclose) this.onclose(e); diff --git a/src/polyfill/RTCDtlsTransport.ts b/src/polyfill/RTCDtlsTransport.ts index db511c0..79f1822 100644 --- a/src/polyfill/RTCDtlsTransport.ts +++ b/src/polyfill/RTCDtlsTransport.ts @@ -1,20 +1,20 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import RTCIceTransport from './RTCIceTransport'; +import RTCPeerConnection from './RTCPeerConnection'; export default class RTCDtlsTransport extends EventTarget implements globalThis.RTCDtlsTransport { - #pc: globalThis.RTCPeerConnection = null; + #pc: RTCPeerConnection = null; #iceTransport = null; onstatechange: globalThis.RTCDtlsTransport['onstatechange'] = null; onerror: globalThis.RTCDtlsTransport['onstatechange'] = null; - constructor(init: { pc: globalThis.RTCPeerConnection; extraFunctions }) { + constructor(init: { pc: RTCPeerConnection }) { super(); this.#pc = init.pc; this.#iceTransport = new RTCIceTransport({ - pc: init.pc, - extraFunctions: init.extraFunctions, + pc: init.pc }); // forward peerConnection events diff --git a/src/polyfill/RTCError.ts b/src/polyfill/RTCError.ts index a015932..b18de67 100644 --- a/src/polyfill/RTCError.ts +++ b/src/polyfill/RTCError.ts @@ -4,6 +4,7 @@ export default class RTCError extends DOMException implements globalThis.RTCErro #sctpCauseCode: number | null; #sdpLineNumber: number | null; #sentAlert: number | null; + #httpRequestStatusCode: number | null; constructor(init: globalThis.RTCErrorInit, message?: string) { super(message, 'OperationError'); @@ -28,6 +29,7 @@ export default class RTCError extends DOMException implements globalThis.RTCErro this.#sctpCauseCode = init.sctpCauseCode ?? null; this.#sdpLineNumber = init.sdpLineNumber ?? null; this.#sentAlert = init.sentAlert ?? null; + this.#httpRequestStatusCode = init.httpRequestStatusCode ?? null; } get errorDetail(): globalThis.RTCErrorDetailType { @@ -54,6 +56,10 @@ export default class RTCError extends DOMException implements globalThis.RTCErro throw new TypeError('Cannot set sctpCauseCode, it is read-only'); } + get httpRequestStatusCode(): number | null { + return this.#httpRequestStatusCode; + } + get sdpLineNumber(): number | null { return this.#sdpLineNumber; } diff --git a/src/polyfill/RTCIceTransport.ts b/src/polyfill/RTCIceTransport.ts index 8fb0cca..95e3335 100644 --- a/src/polyfill/RTCIceTransport.ts +++ b/src/polyfill/RTCIceTransport.ts @@ -1,18 +1,17 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import RTCIceCandidate from './RTCIceCandidate'; +import RTCPeerConnection from './RTCPeerConnection'; export default class RTCIceTransport extends EventTarget implements globalThis.RTCIceTransport { - #pc: globalThis.RTCPeerConnection = null; - #extraFunctions = null; + #pc: RTCPeerConnection = null; ongatheringstatechange: globalThis.RTCIceTransport['ongatheringstatechange'] = null; onselectedcandidatepairchange: globalThis.RTCIceTransport['onselectedcandidatepairchange'] = null; onstatechange: globalThis.RTCIceTransport['onstatechange'] = null; - constructor(init: { pc: globalThis.RTCPeerConnection; extraFunctions }) { + constructor(init: { pc: RTCPeerConnection }) { super(); this.#pc = init.pc; - this.#extraFunctions = init.extraFunctions; this.#pc.addEventListener('icegatheringstatechange', () => { const e = new Event('gatheringstatechange'); @@ -28,7 +27,7 @@ export default class RTCIceTransport extends EventTarget implements globalThis.R get component(): globalThis.RTCIceComponent { const cp = this.getSelectedCandidatePair(); - if (!cp) return null; + if (!cp?.local) return null; return cp.local.component; } @@ -36,8 +35,8 @@ export default class RTCIceTransport extends EventTarget implements globalThis.R return this.#pc ? this.#pc.iceGatheringState : 'new'; } - get role(): string { - return this.#pc.localDescription.type == 'offer' ? 'controlling' : 'controlled'; + get role(): globalThis.RTCIceRole { + return this.#pc.localDescription!.type == 'offer' ? 'controlling' : 'controlled'; } get state(): globalThis.RTCIceTransportState { @@ -45,15 +44,20 @@ export default class RTCIceTransport extends EventTarget implements globalThis.R } getLocalCandidates(): globalThis.RTCIceCandidate[] { - return this.#pc ? this.#extraFunctions.localCandidates() : []; + return this.#pc?.ext_localCandidates ?? []; } - getLocalParameters(): any { - /** */ + getLocalParameters(): RTCIceParameters | null { + return new RTCIceParameters( + new RTCIceCandidate({ + candidate: this.#pc.selectedCandidatePair()!.local.candidate, + sdpMLineIndex: 0, + }), + ); } getRemoteCandidates(): globalThis.RTCIceCandidate[] { - return this.#pc ? this.#extraFunctions.remoteCandidates() : []; + return this.#pc?.ext_remoteCandidates ?? []; } getRemoteParameters(): any { @@ -61,7 +65,7 @@ export default class RTCIceTransport extends EventTarget implements globalThis.R } getSelectedCandidatePair(): globalThis.RTCIceCandidatePair | null { - const cp = this.#extraFunctions.selectedCandidatePair(); + const cp = this.#pc?.selectedCandidatePair(); if (!cp) return null; return { local: new RTCIceCandidate({ @@ -75,3 +79,12 @@ export default class RTCIceTransport extends EventTarget implements globalThis.R }; } } + +export class RTCIceParameters implements globalThis.RTCIceParameters { + usernameFragment = ''; + password = ''; + constructor({ usernameFragment, password = '' }) { + this.usernameFragment = usernameFragment; + this.password = password; + } +} diff --git a/src/polyfill/RTCPeerConnection.ts b/src/polyfill/RTCPeerConnection.ts index a299068..d71ade6 100644 --- a/src/polyfill/RTCPeerConnection.ts +++ b/src/polyfill/RTCPeerConnection.ts @@ -21,13 +21,14 @@ export default class RTCPeerConnection extends EventTarget implements globalThis } #peerConnection: PeerConnection; - #localOffer: any; - #localAnswer: any; + #localOffer: ReturnType; + #localAnswer: ReturnType; #dataChannels: Set; #dataChannelsClosed = 0; #config: globalThis.RTCConfiguration; - #canTrickleIceCandidates: boolean | null; + #canTrickleIceCandidates: boolean | null = null; #sctp: globalThis.RTCSctpTransport; + #announceNegotiation = false; #localCandidates: globalThis.RTCIceCandidate[] = []; #remoteCandidates: globalThis.RTCIceCandidate[] = []; @@ -170,11 +171,11 @@ export default class RTCPeerConnection extends EventTarget implements globalThis this.#peerConnection.onLocalDescription((sdp, type) => { if (type === 'offer') { - this.#localOffer.resolve({ sdp, type }); + this.#localOffer.resolve(new RTCSessionDescription({ sdp, type })); } if (type === 'answer') { - this.#localAnswer.resolve({ sdp, type }); + this.#localAnswer.resolve(new RTCSessionDescription({ sdp, type })); } }); @@ -207,32 +208,43 @@ export default class RTCPeerConnection extends EventTarget implements globalThis this.addEventListener('icecandidate', (e) => { this.onicecandidate?.(e as globalThis.RTCPeerConnectionIceEvent); }); + this.addEventListener('track', (e) => { + this.ontrack?.(e as RTCTrackEvent); + }); + this.addEventListener('negotiationneeded', (e) => { + this.#announceNegotiation = true; + this.onnegotiationneeded?.(e); + }); this.#sctp = new RTCSctpTransport({ pc: this, - extraFunctions: { - maxDataChannelId: (): number => { - return this.#peerConnection.maxDataChannelId(); - }, - maxMessageSize: (): number => { - return this.#peerConnection.maxMessageSize(); - }, - localCandidates: (): globalThis.RTCIceCandidate[] => { - return this.#localCandidates; - }, - remoteCandidates: (): globalThis.RTCIceCandidate[] => { - return this.#remoteCandidates; - }, - selectedCandidatePair: (): { - local: SelectedCandidateInfo; - remote: SelectedCandidateInfo; - } | null => { - return this.#peerConnection.getSelectedCandidatePair(); - }, - }, }); } + // Extra FUnctions + get ext_maxDataChannelId(): number { + return this.#peerConnection.maxDataChannelId(); + } + + get ext_maxMessageSize(): number { + return this.#peerConnection.maxMessageSize(); + } + + get ext_localCandidates(): globalThis.RTCIceCandidate[] { + return this.#localCandidates; + } + + get ext_remoteCandidates(): globalThis.RTCIceCandidate[] { + return this.#remoteCandidates; + } + + selectedCandidatePair(): { + local: SelectedCandidateInfo; + remote: SelectedCandidateInfo; + } | null { + return this.#peerConnection.getSelectedCandidatePair(); + } + get canTrickleIceCandidates(): boolean | null { return this.#canTrickleIceCandidates; } @@ -349,7 +361,7 @@ export default class RTCPeerConnection extends EventTarget implements globalThis return this.#localAnswer; } - createDataChannel(label, opts = {}): RTCDataChannel { + createDataChannel(label: string, opts: globalThis.RTCDataChannelInit = {}): RTCDataChannel { const channel = this.#peerConnection.createDataChannel(label, opts); const dataChannel = new RTCDataChannel(channel, opts); @@ -360,6 +372,11 @@ export default class RTCPeerConnection extends EventTarget implements globalThis this.#dataChannelsClosed++; }); + if (this.#announceNegotiation == null) { + this.#announceNegotiation = false; + this.dispatchEvent(new Event('negotiationneeded')); + } + return dataChannel; } @@ -379,7 +396,7 @@ export default class RTCPeerConnection extends EventTarget implements globalThis throw new DOMException('Not implemented'); } - getStats(): Promise { + getStats(): Promise | any { return new Promise((resolve) => { const report = new Map(); const cp = this.#peerConnection?.getSelectedCandidatePair(); @@ -497,7 +514,7 @@ function createDeferredPromise(): any { return promise; } -function getRandomString(length): string { +function getRandomString(length: number): string { return Math.random() .toString(36) .substring(2, 2 + length); diff --git a/src/polyfill/RTCSctpTransport.ts b/src/polyfill/RTCSctpTransport.ts index 39ef5a5..ce9b121 100644 --- a/src/polyfill/RTCSctpTransport.ts +++ b/src/polyfill/RTCSctpTransport.ts @@ -1,21 +1,19 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import RTCDtlsTransport from './RTCDtlsTransport'; +import RTCPeerConnection from './RTCPeerConnection'; export default class RTCSctpTransport extends EventTarget implements globalThis.RTCSctpTransport { - #pc: globalThis.RTCPeerConnection = null; - #extraFunctions = null; + #pc: RTCPeerConnection = null; #transport: globalThis.RTCDtlsTransport = null; onstatechange: globalThis.RTCSctpTransport['onstatechange'] = null; - constructor(initial: { pc: globalThis.RTCPeerConnection; extraFunctions }) { + constructor(initial: { pc: RTCPeerConnection }) { super(); this.#pc = initial.pc; - this.#extraFunctions = initial.extraFunctions; this.#transport = new RTCDtlsTransport({ - pc: initial.pc, - extraFunctions: initial.extraFunctions, + pc: initial.pc }); this.#pc.addEventListener('connectionstatechange', () => { @@ -27,12 +25,12 @@ export default class RTCSctpTransport extends EventTarget implements globalThis. get maxChannels(): number | null { if (this.state !== 'connected') return null; - return this.#pc ? this.#extraFunctions.maxDataChannelId() : 0; + return this.#pc.ext_maxDataChannelId; } get maxMessageSize(): number { if (this.state !== 'connected') return null; - return this.#pc ? this.#extraFunctions.maxMessageSize() : 0; + return this.#pc?.ext_maxMessageSize ?? 65536;; } get state(): globalThis.RTCSctpTransportState { diff --git a/src/polyfill/RTCSessionDescription.ts b/src/polyfill/RTCSessionDescription.ts index 800e211..6e9a157 100644 --- a/src/polyfill/RTCSessionDescription.ts +++ b/src/polyfill/RTCSessionDescription.ts @@ -11,14 +11,23 @@ export default class RTCSessionDescription implements globalThis.RTCSessionDescr #sdp: string; constructor(init: globalThis.RTCSessionDescriptionInit) { - this.#type = init ? init.type : null; - this.#sdp = init ? init.sdp : null; + this.#type = init?.type; + this.#sdp = init?.sdp ?? ''; } get type(): globalThis.RTCSdpType { return this.#type; } + set type(type) { + if (type !== 'offer' && type !== 'answer' && type !== 'pranswer' && type !== 'rollback') { + throw new TypeError( + `Failed to set the 'type' property on 'RTCSessionDescription': The provided value '${type}' is not a valid enum value of type RTCSdpType.`, + ); + } + this.#type = type; + } + get sdp(): string { return this.#sdp; } From 0d3fa732d9f863f0ebcd6a84c8b5b6b9207058fd Mon Sep 17 00:00:00 2001 From: Murat Dogan Date: Sat, 24 May 2025 21:26:55 +0200 Subject: [PATCH 2/2] package json --- package-lock.json | 7 +++++++ package.json | 1 + 2 files changed, 8 insertions(+) diff --git a/package-lock.json b/package-lock.json index a52c70a..c68807c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@rollup/plugin-replace": "^6.0.1", "@types/jest": "^29.5.12", "@types/node": "^20.6.1", + "@types/webrtc": "^0.0.46", "@typescript-eslint/eslint-plugin": "^7.17.0", "@typescript-eslint/parser": "^7.17.0", "cmake-js": "^7.3.0", @@ -1716,6 +1717,12 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, + "node_modules/@types/webrtc": { + "version": "0.0.46", + "resolved": "https://registry.npmjs.org/@types/webrtc/-/webrtc-0.0.46.tgz", + "integrity": "sha512-cvCnjKy0ma9ODWbVYBTMMB+WkocwLcscCwn2/caDVdb9MWaesS8PYGapIIHFsAaIBXRFlH1Fc7ZjIBO6pH0HKA==", + "dev": true + }, "node_modules/@types/yargs": { "version": "17.0.24", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", diff --git a/package.json b/package.json index 5004079..5e142c2 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "@rollup/plugin-replace": "^6.0.1", "@types/jest": "^29.5.12", "@types/node": "^20.6.1", + "@types/webrtc": "^0.0.46", "@typescript-eslint/eslint-plugin": "^7.17.0", "@typescript-eslint/parser": "^7.17.0", "cmake-js": "^7.3.0",