Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: polyfill W3C compatibility #324

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

ThaUnknown
Copy link
Contributor

@ThaUnknown ThaUnknown commented Jan 20, 2025

this PR aims to fix all the critical W3C compatibility issues in the polyfill

it also implements rudimentary MediaStreams [WIP]

I'll add comments to the PR explaining things

if (arguments.length === 0) throw new TypeError(`Failed to construct 'RTCDataChannelEvent': 2 arguments required, but only ${arguments.length} present.`)
if (typeof init !== 'object') throw new TypeError("Failed to construct 'RTCDataChannelEvent': The provided value is not of type 'RTCDataChannelEventInit'.")
if (!init.channel) throw new TypeError("Failed to construct 'RTCDataChannelEvent': Failed to read the 'channel' property from 'RTCDataChannelEventInit': Required member is undefined.")
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'.")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

required by spec

onclosing: globalThis.RTCDataChannel['onclosing'];
onerror: globalThis.RTCDataChannel['onerror'];
onmessage: globalThis.RTCDataChannel['onmessage'];
onopen: globalThis.RTCDataChannel['onopen']
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bettter to let the types define it, rather than manually declare any, a typed event target implementation would be prefered however

});
// we need updated connectionstate, so this is delayed by a single event loop tick
// this is fucked and wonky, needs to be made better
this.#dataChannel.onClosed(() => setTimeout(() => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the spec is very assine about how and when datachannels should close, this is the closest i was able to bring it inline with said spec, the order of events in peer, ice and dc on closing is important for some libraries

if (!ArrayBuffer.isView(message)) {
data = message
} else if (this.#binaryType === 'blob') {
data = new Blob([message])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't explicitly use buffer, also cast to Blob when the type requires it, this wasn't even done before

this.#dataChannel.sendMessage(data);
} else if (data instanceof Blob) {
} else if ('arrayBuffer' in data) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we're not actually interested in blob, but its ab method, this is important for Blob-like libraries

state = 'closed';
}
return state;
if (this.#pc.connectionState === 'disconnected') return 'closed'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pc is always defined

@@ -58,7 +58,7 @@ export default class RTCIceCandidate implements globalThis.RTCIceCandidate {
}

get address(): string | null {
return this.#address || null;
return this.#address ?? null;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

|| was incorrect to use here!!! we want null checking, not falsy checking

});
}

get component(): globalThis.RTCIceComponent {
const cp = this.getSelectedCandidatePair();
if (!cp) return null;
if (!cp?.local) return null;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didnt verify local!

getRemoteParameters(): any {
/** */
getRemoteParameters(): RTCIceParameters | null {
return new RTCIceParameters(new RTCIceCandidate({ candidate: this.#pc.getSelectedCandidatePair().remote.candidate, sdpMLineIndex: 0 }))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RTCIceParams were completely missing, these still dont have password/ufrag, need to wait for upstream to expose that

}

get maxMessageSize(): number {
if (this.state !== 'connected') return null;
return this.#pc ? this.#extraFunctions.maxMessageSize() : 0;
return this.#pc.maxMessageSize ?? 65536;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should never be 0

this.#type = init ? init.type : null;
this.#sdp = init ? init.sdp : null;
constructor(init: globalThis.RTCSessionDescriptionInit | null | undefined) {
this.#type = init?.type;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worked for the library, but not as a global exposed constructor

// length of urls can not be 0
if (config.iceServers[i].urls?.length === 0)
throw new exceptions.SyntaxError('IceServers urls cannot be empty');
setConfiguration(config: RTCConfiguration): void {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I for the most part removed the old implementation, because by default a lot of fallbacks are used, this is correct for W3C, but i'm not certain this is correct for NDC, this needs to be verified

});

this.#peerConnection.onLocalDescription((sdp, type) => {
if (type === 'offer') {
this.#localOffer.resolve({ sdp, type });
this.#localOffer.resolve(new RTCSessionDescription({ sdp, type }));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was incorrect!

this.ontrack?.(e as RTCTrackEvent)
})

this.addEventListener('negotiationneeded', e => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was never implemented, and is required for polite-peer libs

}

createOffer(): Promise<globalThis.RTCSessionDescriptionInit | any> {
createOffer(): Promise<globalThis.RTCSessionDescriptionInit> & Promise<void> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are some annoying deprecated overloads for some of these methods, we shouldn't support them

@murat-dogan
Copy link
Owner

Hello and thank you for the PR.
Unfortunately, I can not compile the changes and run the WPT tests with these changes.
I wanted to see first which tests we will achieve now to compare here.
https://github.com/murat-dogan/node-datachannel/tree/master/test/wpt-tests

I think it will be good in all cases to separate this PR into small PRs. Especially RTCRtp part.
What do you think? Then we can handle it one by one and see which tests we can now achieve.

/home/murat/js/fork/node-datachannel/node_modules/ts-node/src/index.ts:859
    return new TSError(diagnosticText, diagnosticCodes, diagnostics);
           ^
TSError: ⨯ Unable to compile TypeScript:
src/polyfill/RTCRtp.ts:45:23 - error TS2339: Property 'close' does not exist on type 'Audio | Video | Track'.
  Property 'close' does not exist on type 'Audio'.

45     this.#transceiver.close?.()
                         ~~~~~
src/polyfill/RTCRtp.ts:53:5 - error TS2322: Type 'Audio | Video | Track' is not assignable to type 'Audio | Video'.
  Type 'Track' is not assignable to type 'Audio | Video'.
    Type 'Track' is missing the following properties from type 'Video': addVideoCodec, addH264Codec, addVP8Codec, addVP9Codec, and 16 more.

53     return this.#transceiver
       ~~~~~~

    at createTSError (/home/murat/js/fork/node-datachannel/node_modules/ts-node/src/index.ts:859:12)
    at reportTSError (/home/murat/js/fork/node-datachannel/node_modules/ts-node/src/index.ts:863:19)
    at getOutput (/home/murat/js/fork/node-datachannel/node_modules/ts-node/src/index.ts:1077:36)
    at Object.compile (/home/murat/js/fork/node-datachannel/node_modules/ts-node/src/index.ts:1433:41)
    at Module.m._compile (/home/murat/js/fork/node-datachannel/node_modules/ts-node/src/index.ts:1617:30)
    at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
    at Object.require.extensions.<computed> [as .ts] (/home/murat/js/fork/node-datachannel/node_modules/ts-node/src/index.ts:1621:12)
    at Module.load (node:internal/modules/cjs/loader:1207:32)
    at Function.Module._load (node:internal/modules/cjs/loader:1023:12)
    at Module.require (node:internal/modules/cjs/loader:1235:19) {
  diagnosticCodes: [ 2339, 2322 ]
}

@ThaUnknown
Copy link
Contributor Author

I assumed you use some manual commands, because rollup isn't even installable as you include a package-lock, and removing it, causes version conflict errors, so I couldn't even run rollup to try compiling it

@ThaUnknown
Copy link
Contributor Author

you can feel free to split up this PR, I kinda lost my will to live after implementing the video/audio track shenanigans, mainly because of how they are exposed, I think the bindings need to do it a bit better than what we currently have, but that's honestly above what I'm willing to do, so I tried somewhat finishing it, even tho I don't think it actually works correctly as I couldn't find how to properly create and consume tracks and transceivers

@murat-dogan
Copy link
Owner

I assumed you use some manual commands, because rollup isn't even installable as you include a package-lock, and removing it, causes version conflict errors, so I couldn't even run rollup to try compiling it

I didn't understand what you mean.
to build all npm run build
to build only tsc npm run build:tsc

for others you can check package.json

@murat-dogan
Copy link
Owner

you can feel free to split up this PR, I kinda lost my will to live after implementing the video/audio track shenanigans, mainly because of how they are exposed, I think the bindings need to do it a bit better than what we currently have, but that's honestly above what I'm willing to do, so I tried somewhat finishing it, even tho I don't think it actually works correctly as I couldn't find how to properly create and consume tracks and transceivers

Unfortunately, I can not split the PR.
I will welcome any changes that you suggest and that will make more tests to be achieved.

About the tracks, we need definitely a separate PR and work and of course some test cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants