Skip to content

Commit 076764a

Browse files
committed
fix: polyfill W3C compatibility
1 parent dd3954e commit 076764a

15 files changed

+969
-441
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
"@rollup/plugin-replace": "^6.0.1",
8080
"@types/jest": "^29.5.12",
8181
"@types/node": "^20.6.1",
82+
"@types/webrtc": "^0.0.44",
8283
"@typescript-eslint/eslint-plugin": "^7.17.0",
8384
"@typescript-eslint/parser": "^7.17.0",
8485
"cmake-js": "^7.3.0",
@@ -104,4 +105,4 @@
104105
"dependencies": {
105106
"prebuild-install": "^7.1.2"
106107
}
107-
}
108+
}

src/polyfill/Events.ts

+48-5
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,63 @@ export class RTCPeerConnectionIceEvent extends Event implements globalThis.RTCPe
1414
get candidate(): RTCIceCandidate {
1515
return this.#candidate;
1616
}
17+
18+
get url (): string {
19+
return '' // TODO ?
20+
}
1721
}
1822

1923
export class RTCDataChannelEvent extends Event implements globalThis.RTCDataChannelEvent {
2024
#channel: RTCDataChannel;
2125

22-
constructor(type: string, eventInitDict: globalThis.RTCDataChannelEventInit) {
23-
super(type);
26+
// type is defined as a consturctor, but always overwritten, interesting spec
27+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
28+
constructor(_type: string = 'datachannel', init: globalThis.RTCDataChannelEventInit) {
29+
if (arguments.length === 0) throw new TypeError(`Failed to construct 'RTCDataChannelEvent': 2 arguments required, but only ${arguments.length} present.`)
30+
if (typeof init !== 'object') throw new TypeError("Failed to construct 'RTCDataChannelEvent': The provided value is not of type 'RTCDataChannelEventInit'.")
31+
if (!init.channel) throw new TypeError("Failed to construct 'RTCDataChannelEvent': Failed to read the 'channel' property from 'RTCDataChannelEventInit': Required member is undefined.")
32+
if (init.channel.constructor !== RTCDataChannel) throw new TypeError("Failed to construct 'RTCDataChannelEvent': Failed to read the 'channel' property from 'RTCDataChannelEventInit': Failed to convert value to 'RTCDataChannel'.")
33+
super('datachannel')
2434

25-
if (type && !eventInitDict.channel) throw new TypeError('channel member is required');
26-
27-
this.#channel = eventInitDict?.channel as RTCDataChannel;
35+
this.#channel = init.channel;
2836
}
2937

3038
get channel(): RTCDataChannel {
3139
return this.#channel;
3240
}
3341
}
42+
43+
export class RTCErrorEvent extends Event implements globalThis.RTCErrorEvent {
44+
#error: RTCError
45+
constructor (type: string, init: globalThis.RTCErrorEventInit) {
46+
if (arguments.length < 2) throw new TypeError(`Failed to construct 'RTCErrorEvent': 2 arguments required, but only ${arguments.length} present.`)
47+
if (typeof init !== 'object') throw new TypeError("Failed to construct 'RTCErrorEvent': The provided value is not of type 'RTCErrorEventInit'.")
48+
if (!init.error) throw new TypeError("Failed to construct 'RTCErrorEvent': Failed to read the 'error' property from 'RTCErrorEventInit': Required member is undefined.")
49+
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'.")
50+
super(type || 'error')
51+
this.#error = init.error
52+
}
53+
54+
get error (): RTCError {
55+
return this.#error
56+
}
57+
}
58+
59+
export class MediaStreamTrackEvent extends Event implements globalThis.MediaStreamTrackEvent {
60+
#track: MediaStreamTrack
61+
62+
constructor (type, init) {
63+
if (arguments.length === 0) throw new TypeError(`Failed to construct 'MediaStreamTrackEvent': 2 arguments required, but only ${arguments.length} present.`)
64+
if (typeof init !== 'object') throw new TypeError("Failed to construct 'MediaStreamTrackEvent': The provided value is not of type 'MediaStreamTrackEventInit'.")
65+
if (!init.track) throw new TypeError("Failed to construct 'MediaStreamTrackEvent': Failed to read the 'track' property from 'MediaStreamTrackEventInit': Required member is undefined.")
66+
if (init.track.constructor !== MediaStreamTrack) throw new TypeError("Failed to construct 'MediaStreamTrackEvent': Failed to read the 'channel' property from 'MediaStreamTrackEventInit': Failed to convert value to 'RTCDataChannel'.")
67+
68+
super(type)
69+
70+
this.#track = init.track
71+
}
72+
73+
get track (): MediaStreamTrack {
74+
return this.#track
75+
}
76+
}

src/polyfill/MediaStream.ts

+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { Readable } from 'node:stream'
2+
import { MediaStreamTrackEvent } from './Events.js'
3+
import { Track } from '../lib/index.js'
4+
5+
export class MediaStreamTrack extends EventTarget implements globalThis.MediaStreamTrack {
6+
media
7+
track: Track
8+
stream = new Readable({ read: () => {} })
9+
#kind: string
10+
#label: string
11+
#id = crypto.randomUUID()
12+
contentHint = ''
13+
14+
onmute
15+
onunmute
16+
onended
17+
18+
constructor ({ kind, label }: { kind: string, label: string }) {
19+
super()
20+
if (!kind) throw new TypeError("Failed to construct 'MediaStreamTrack': Failed to read the 'kind' property from 'MediaStreamTrackInit': Required member is undefined.")
21+
this.#kind = kind
22+
this.#label = label
23+
24+
this.addEventListener('ended', e => {
25+
this.onended?.(e)
26+
this.track?.close()
27+
this.stream.destroy()
28+
})
29+
this.stream.on('close', () => {
30+
this.stop()
31+
})
32+
}
33+
34+
async applyConstraints (): Promise<void> {
35+
console.warn('Constraints unsupported, ignored')
36+
}
37+
38+
stop (): void {
39+
this.track?.close()
40+
this.stream.destroy()
41+
this.dispatchEvent(new Event('ended'))
42+
}
43+
44+
getSettings (): globalThis.MediaTrackSettings {
45+
console.warn('Settings upsupported, ignored')
46+
return {}
47+
}
48+
49+
getConstraints (): globalThis.MediaTrackConstraints {
50+
console.warn('Constraints unsupported, ignored')
51+
return {}
52+
}
53+
54+
getCapabilities (): globalThis.MediaTrackCapabilities {
55+
console.warn('Capabilities unsupported, ignored')
56+
return {}
57+
}
58+
59+
clone (): this {
60+
console.warn('Track clonning is unsupported, returned this instance')
61+
return this
62+
}
63+
64+
get kind (): string {
65+
return this.#kind
66+
}
67+
68+
get enabled (): boolean | null {
69+
return this.track?.isOpen()
70+
}
71+
72+
set enabled (_) {
73+
console.warn('Track enabling and disabling is unsupported, ignored')
74+
}
75+
76+
get muted (): boolean {
77+
return false
78+
}
79+
80+
get id (): string {
81+
return this.#id
82+
}
83+
84+
get label (): string {
85+
return this.#label
86+
}
87+
88+
get readyState (): 'ended' | 'live' {
89+
return this.track?.isClosed() ? 'ended' : 'live'
90+
}
91+
}
92+
93+
/**
94+
* @class
95+
* @implements {globalThis.MediaStream}
96+
*/
97+
export class MediaStream extends EventTarget {
98+
#active = true
99+
#id = crypto.randomUUID()
100+
#tracks = new Set<MediaStreamTrack>()
101+
onaddtrack
102+
onremovetrack
103+
onactive
104+
oninactive
105+
106+
constructor (streamOrTracks) {
107+
super()
108+
if (streamOrTracks instanceof MediaStream) {
109+
for (const track of streamOrTracks.getTracks()) {
110+
this.addTrack(track)
111+
}
112+
} else if (Array.isArray(streamOrTracks)) {
113+
for (const track of streamOrTracks) {
114+
this.addTrack(track)
115+
}
116+
}
117+
this.addEventListener('active', e => {
118+
this.onactive?.(e)
119+
})
120+
this.addEventListener('inactive', e => {
121+
this.oninactive?.(e)
122+
})
123+
this.addEventListener('removetrack', e => {
124+
this.onremovetrack?.(e)
125+
})
126+
this.addEventListener('addtrack', e => {
127+
this.onaddtrack?.(e)
128+
})
129+
this.dispatchEvent(new Event('active'))
130+
}
131+
132+
get active (): boolean {
133+
return this.#active
134+
}
135+
136+
get id (): string {
137+
return this.#id
138+
}
139+
140+
addTrack (track) {
141+
this.#tracks.add(track)
142+
this.dispatchEvent(new MediaStreamTrackEvent('addtrack', { track }))
143+
}
144+
145+
getTracks (): MediaStreamTrack[] {
146+
return [...this.#tracks]
147+
}
148+
149+
getVideoTracks (): MediaStreamTrack[] {
150+
return [...this.#tracks].filter(({ kind }) => kind === 'video')
151+
}
152+
153+
getAudioTracks (): MediaStreamTrack[] {
154+
return [...this.#tracks].filter(({ kind }) => kind === 'audio')
155+
}
156+
157+
getTrackById (id): MediaStreamTrack {
158+
return [...this.#tracks].find(track => track.id === id) ?? null
159+
}
160+
161+
removeTrack (track): void {
162+
this.#tracks.delete(track)
163+
this.dispatchEvent(new MediaStreamTrackEvent('removetrack', { track }))
164+
}
165+
166+
clone (): MediaStream {
167+
return new MediaStream([...this.getTracks()])
168+
}
169+
170+
stop ():void {
171+
for (const track of this.getTracks()) {
172+
track.stop()
173+
}
174+
this.#active = false
175+
this.dispatchEvent(new Event('inactive'))
176+
}
177+
}

src/polyfill/RTCCertificate.ts

+4
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,8 @@ export default class RTCCertificate implements globalThis.RTCCertificate {
1414
getFingerprints(): globalThis.RTCDtlsFingerprint[] {
1515
return this.#fingerprints;
1616
}
17+
18+
getAlgorithm (): string {
19+
return ''
20+
}
1721
}

0 commit comments

Comments
 (0)