node-datachannel 0.27.0 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +25 -25
- package/.prettierignore +4 -0
- package/.prettierrc +1 -0
- package/API.md +4 -4
- package/BULDING.md +11 -2
- package/CMakeLists.txt +9 -4
- package/README.md +22 -18
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/lib/datachannel-stream.cjs +13 -13
- package/dist/cjs/lib/datachannel-stream.cjs.map +1 -1
- package/dist/cjs/lib/index.cjs.map +1 -1
- package/dist/cjs/lib/websocket-server.cjs.map +1 -1
- package/dist/cjs/lib/websocket.cjs.map +1 -1
- package/dist/cjs/polyfill/Events.cjs +52 -3
- package/dist/cjs/polyfill/Events.cjs.map +1 -1
- package/dist/cjs/polyfill/Exception.cjs.map +1 -1
- package/dist/cjs/polyfill/RTCCertificate.cjs +5 -2
- package/dist/cjs/polyfill/RTCCertificate.cjs.map +1 -1
- package/dist/cjs/polyfill/RTCDataChannel.cjs +27 -19
- package/dist/cjs/polyfill/RTCDataChannel.cjs.map +1 -1
- package/dist/cjs/polyfill/RTCDtlsTransport.cjs +6 -5
- package/dist/cjs/polyfill/RTCDtlsTransport.cjs.map +1 -1
- package/dist/cjs/polyfill/RTCError.cjs +9 -2
- package/dist/cjs/polyfill/RTCError.cjs.map +1 -1
- package/dist/cjs/polyfill/RTCIceCandidate.cjs +13 -8
- package/dist/cjs/polyfill/RTCIceCandidate.cjs.map +1 -1
- package/dist/cjs/polyfill/RTCIceTransport.cjs +26 -16
- package/dist/cjs/polyfill/RTCIceTransport.cjs.map +1 -1
- package/dist/cjs/polyfill/RTCPeerConnection.cjs +80 -70
- package/dist/cjs/polyfill/RTCPeerConnection.cjs.map +1 -1
- package/dist/cjs/polyfill/RTCSctpTransport.cjs +9 -11
- package/dist/cjs/polyfill/RTCSctpTransport.cjs.map +1 -1
- package/dist/cjs/polyfill/RTCSessionDescription.cjs +10 -2
- package/dist/cjs/polyfill/RTCSessionDescription.cjs.map +1 -1
- package/dist/cjs/polyfill/index.cjs.map +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/lib/datachannel-stream.mjs.map +1 -1
- package/dist/esm/lib/index.mjs.map +1 -1
- package/dist/esm/lib/websocket-server.mjs.map +1 -1
- package/dist/esm/lib/websocket.mjs.map +1 -1
- package/dist/esm/polyfill/Events.mjs +52 -4
- package/dist/esm/polyfill/Events.mjs.map +1 -1
- package/dist/esm/polyfill/Exception.mjs.map +1 -1
- package/dist/esm/polyfill/RTCCertificate.mjs +5 -2
- package/dist/esm/polyfill/RTCCertificate.mjs.map +1 -1
- package/dist/esm/polyfill/RTCDataChannel.mjs +27 -19
- package/dist/esm/polyfill/RTCDataChannel.mjs.map +1 -1
- package/dist/esm/polyfill/RTCDtlsTransport.mjs +6 -5
- package/dist/esm/polyfill/RTCDtlsTransport.mjs.map +1 -1
- package/dist/esm/polyfill/RTCError.mjs +9 -2
- package/dist/esm/polyfill/RTCError.mjs.map +1 -1
- package/dist/esm/polyfill/RTCIceCandidate.mjs +13 -8
- package/dist/esm/polyfill/RTCIceCandidate.mjs.map +1 -1
- package/dist/esm/polyfill/RTCIceTransport.mjs +26 -17
- package/dist/esm/polyfill/RTCIceTransport.mjs.map +1 -1
- package/dist/esm/polyfill/RTCPeerConnection.mjs +80 -70
- package/dist/esm/polyfill/RTCPeerConnection.mjs.map +1 -1
- package/dist/esm/polyfill/RTCSctpTransport.mjs +9 -11
- package/dist/esm/polyfill/RTCSctpTransport.mjs.map +1 -1
- package/dist/esm/polyfill/RTCSessionDescription.mjs +10 -2
- package/dist/esm/polyfill/RTCSessionDescription.mjs.map +1 -1
- package/dist/esm/polyfill/index.mjs.map +1 -1
- package/dist/types/lib/types.d.ts +6 -6
- package/dist/types/polyfill/Events.d.ts +4 -1
- package/dist/types/polyfill/RTCCertificate.d.ts +1 -0
- package/dist/types/polyfill/RTCDataChannel.d.ts +6 -6
- package/dist/types/polyfill/RTCDtlsTransport.d.ts +5 -4
- package/dist/types/polyfill/RTCError.d.ts +1 -0
- package/dist/types/polyfill/RTCIceCandidate.d.ts +1 -1
- package/dist/types/polyfill/RTCIceTransport.d.ts +17 -8
- package/dist/types/polyfill/RTCPeerConnection.d.ts +22 -11
- package/dist/types/polyfill/RTCSctpTransport.d.ts +4 -3
- package/dist/types/polyfill/RTCSessionDescription.d.ts +1 -0
- package/jest.config.ts +8 -8
- package/package.json +4 -2
- package/rollup.config.mjs +1 -1
- package/src/cpp/data-channel-wrapper.cpp +11 -0
- package/src/index.ts +0 -1
- package/src/lib/datachannel-stream.ts +83 -84
- package/src/lib/index.ts +163 -140
- package/src/lib/types.ts +97 -72
- package/src/lib/websocket-server.ts +25 -25
- package/src/lib/websocket.ts +19 -19
- package/src/polyfill/Events.ts +67 -17
- package/src/polyfill/Exception.ts +24 -24
- package/src/polyfill/README.md +8 -8
- package/src/polyfill/RTCCertificate.ts +16 -12
- package/src/polyfill/RTCDataChannel.ts +198 -192
- package/src/polyfill/RTCDtlsTransport.ts +39 -39
- package/src/polyfill/RTCError.ts +63 -56
- package/src/polyfill/RTCIceCandidate.ts +120 -116
- package/src/polyfill/RTCIceTransport.ts +72 -64
- package/src/polyfill/RTCPeerConnection.ts +436 -420
- package/src/polyfill/RTCSctpTransport.ts +44 -47
- package/src/polyfill/RTCSessionDescription.ts +26 -17
- package/src/polyfill/index.ts +22 -22
|
@@ -11,495 +11,511 @@ import RTCCertificate from './RTCCertificate';
|
|
|
11
11
|
|
|
12
12
|
// extend RTCConfiguration with peerIdentity
|
|
13
13
|
interface RTCConfiguration extends globalThis.RTCConfiguration {
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
peerIdentity?: string;
|
|
15
|
+
peerConnection?: PeerConnection;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export default class RTCPeerConnection extends EventTarget implements globalThis.RTCPeerConnection {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
static async generateCertificate(): Promise<RTCCertificate> {
|
|
20
|
+
throw new DOMException('Not implemented');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#peerConnection: PeerConnection;
|
|
24
|
+
#localOffer: ReturnType<typeof createDeferredPromise>;
|
|
25
|
+
#localAnswer: ReturnType<typeof createDeferredPromise>;
|
|
26
|
+
#dataChannels: Set<globalThis.RTCDataChannel>;
|
|
27
|
+
#dataChannelsClosed = 0;
|
|
28
|
+
#config: globalThis.RTCConfiguration;
|
|
29
|
+
#canTrickleIceCandidates: boolean | null = null;
|
|
30
|
+
#sctp: globalThis.RTCSctpTransport;
|
|
31
|
+
#announceNegotiation = false;
|
|
32
|
+
|
|
33
|
+
#localCandidates: globalThis.RTCIceCandidate[] = [];
|
|
34
|
+
#remoteCandidates: globalThis.RTCIceCandidate[] = [];
|
|
35
|
+
|
|
36
|
+
// events
|
|
37
|
+
onconnectionstatechange: globalThis.RTCPeerConnection['onconnectionstatechange'] = null;
|
|
38
|
+
// For ondatachannel we need to define type manually
|
|
39
|
+
ondatachannel: ((this: globalThis.RTCPeerConnection, ev: RTCDataChannelEvent) => any) | null;
|
|
40
|
+
onicecandidate: globalThis.RTCPeerConnection['onicecandidate'] = null;
|
|
41
|
+
onicecandidateerror: globalThis.RTCPeerConnection['onicecandidateerror'] = null;
|
|
42
|
+
oniceconnectionstatechange: globalThis.RTCPeerConnection['oniceconnectionstatechange'] = null;
|
|
43
|
+
onicegatheringstatechange: globalThis.RTCPeerConnection['onicegatheringstatechange'] = null;
|
|
44
|
+
onnegotiationneeded: globalThis.RTCPeerConnection['onnegotiationneeded'] = null;
|
|
45
|
+
onsignalingstatechange: globalThis.RTCPeerConnection['onsignalingstatechange'] = null;
|
|
46
|
+
ontrack: globalThis.RTCPeerConnection['ontrack'] = null;
|
|
47
|
+
|
|
48
|
+
private _checkConfiguration(config: globalThis.RTCConfiguration): void {
|
|
49
|
+
if (config && config.iceServers === undefined) config.iceServers = [];
|
|
50
|
+
if (config && config.iceTransportPolicy === undefined) config.iceTransportPolicy = 'all';
|
|
51
|
+
|
|
52
|
+
if (config?.iceServers === null) throw new TypeError('IceServers cannot be null');
|
|
53
|
+
|
|
54
|
+
// Check for all the properties of iceServers
|
|
55
|
+
if (Array.isArray(config?.iceServers)) {
|
|
56
|
+
for (let i = 0; i < config.iceServers.length; i++) {
|
|
57
|
+
if (config.iceServers[i] === null) throw new TypeError('IceServers cannot be null');
|
|
58
|
+
if (config.iceServers[i] === undefined)
|
|
59
|
+
throw new TypeError('IceServers cannot be undefined');
|
|
60
|
+
if (Object.keys(config.iceServers[i]).length === 0)
|
|
61
|
+
throw new TypeError('IceServers cannot be empty');
|
|
62
|
+
|
|
63
|
+
// If iceServers is string convert to array
|
|
64
|
+
if (typeof config.iceServers[i].urls === 'string')
|
|
65
|
+
config.iceServers[i].urls = [config.iceServers[i].urls as string];
|
|
66
|
+
|
|
67
|
+
// urls can not be empty
|
|
68
|
+
if ((config.iceServers[i].urls as string[])?.some((url) => url == ''))
|
|
69
|
+
throw new exceptions.SyntaxError('IceServers urls cannot be empty');
|
|
70
|
+
|
|
71
|
+
// urls should be valid URLs and match the protocols "stun:|turn:|turns:"
|
|
72
|
+
if (
|
|
73
|
+
(config.iceServers[i].urls as string[])?.some((url) => {
|
|
74
|
+
try {
|
|
75
|
+
const parsedURL = new URL(url);
|
|
22
76
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
#dataChannels: Set<globalThis.RTCDataChannel>;
|
|
27
|
-
#dataChannelsClosed = 0;
|
|
28
|
-
#config: globalThis.RTCConfiguration;
|
|
29
|
-
#canTrickleIceCandidates: boolean | null;
|
|
30
|
-
#sctp: globalThis.RTCSctpTransport;
|
|
31
|
-
|
|
32
|
-
#localCandidates: globalThis.RTCIceCandidate[] = [];
|
|
33
|
-
#remoteCandidates: globalThis.RTCIceCandidate[] = [];
|
|
34
|
-
|
|
35
|
-
// events
|
|
36
|
-
onconnectionstatechange: ((this: globalThis.RTCPeerConnection, ev: Event) => any) | null;
|
|
37
|
-
ondatachannel: ((this: globalThis.RTCPeerConnection, ev: globalThis.RTCDataChannelEvent) => any) | null;
|
|
38
|
-
onicecandidate: ((this: globalThis.RTCPeerConnection, ev: globalThis.RTCPeerConnectionIceEvent) => any) | null;
|
|
39
|
-
onicecandidateerror: ((this: globalThis.RTCPeerConnection, ev: Event) => any) | null;
|
|
40
|
-
oniceconnectionstatechange: ((this: globalThis.RTCPeerConnection, ev: Event) => any) | null;
|
|
41
|
-
onicegatheringstatechange: ((this: globalThis.RTCPeerConnection, ev: Event) => any) | null;
|
|
42
|
-
onnegotiationneeded: ((this: globalThis.RTCPeerConnection, ev: Event) => any) | null;
|
|
43
|
-
onsignalingstatechange: ((this: globalThis.RTCPeerConnection, ev: Event) => any) | null;
|
|
44
|
-
ontrack: ((this: globalThis.RTCPeerConnection, ev: globalThis.RTCTrackEvent) => any) | null;
|
|
45
|
-
|
|
46
|
-
private _checkConfiguration(config: globalThis.RTCConfiguration): void {
|
|
47
|
-
if (config && config.iceServers === undefined) config.iceServers = [];
|
|
48
|
-
if (config && config.iceTransportPolicy === undefined) config.iceTransportPolicy = 'all';
|
|
49
|
-
|
|
50
|
-
if (config?.iceServers === null) throw new TypeError('IceServers cannot be null');
|
|
51
|
-
|
|
52
|
-
// Check for all the properties of iceServers
|
|
53
|
-
if (Array.isArray(config?.iceServers)) {
|
|
54
|
-
for (let i = 0; i < config.iceServers.length; i++) {
|
|
55
|
-
if (config.iceServers[i] === null) throw new TypeError('IceServers cannot be null');
|
|
56
|
-
if (config.iceServers[i] === undefined) throw new TypeError('IceServers cannot be undefined');
|
|
57
|
-
if (Object.keys(config.iceServers[i]).length === 0) throw new TypeError('IceServers cannot be empty');
|
|
58
|
-
|
|
59
|
-
// If iceServers is string convert to array
|
|
60
|
-
if (typeof config.iceServers[i].urls === 'string')
|
|
61
|
-
config.iceServers[i].urls = [config.iceServers[i].urls as string];
|
|
62
|
-
|
|
63
|
-
// urls can not be empty
|
|
64
|
-
if ((config.iceServers[i].urls as string[])?.some((url) => url == ''))
|
|
65
|
-
throw new exceptions.SyntaxError('IceServers urls cannot be empty');
|
|
66
|
-
|
|
67
|
-
// urls should be valid URLs and match the protocols "stun:|turn:|turns:"
|
|
68
|
-
if (
|
|
69
|
-
(config.iceServers[i].urls as string[])?.some(
|
|
70
|
-
(url) => {
|
|
71
|
-
try {
|
|
72
|
-
const parsedURL = new URL(url)
|
|
73
|
-
|
|
74
|
-
return !/^(stun:|turn:|turns:)$/.test(parsedURL.protocol)
|
|
75
|
-
} catch (error) {
|
|
76
|
-
return true
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
)
|
|
80
|
-
)
|
|
81
|
-
throw new exceptions.SyntaxError('IceServers urls wrong format');
|
|
82
|
-
|
|
83
|
-
// If this is a turn server check for username and credential
|
|
84
|
-
if ((config.iceServers[i].urls as string[])?.some((url) => url.startsWith('turn'))) {
|
|
85
|
-
if (!config.iceServers[i].username)
|
|
86
|
-
throw new exceptions.InvalidAccessError('IceServers username cannot be null');
|
|
87
|
-
if (!config.iceServers[i].credential)
|
|
88
|
-
throw new exceptions.InvalidAccessError('IceServers username cannot be undefined');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// length of urls can not be 0
|
|
92
|
-
if (config.iceServers[i].urls?.length === 0)
|
|
93
|
-
throw new exceptions.SyntaxError('IceServers urls cannot be empty');
|
|
77
|
+
return !/^(stun:|turn:|turns:)$/.test(parsedURL.protocol);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return true;
|
|
94
80
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (
|
|
98
|
-
config &&
|
|
99
|
-
config.iceTransportPolicy &&
|
|
100
|
-
config.iceTransportPolicy !== 'all' &&
|
|
101
|
-
config.iceTransportPolicy !== 'relay'
|
|
81
|
+
})
|
|
102
82
|
)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
constructor(config: RTCConfiguration = { iceServers: [], iceTransportPolicy: 'all' }) {
|
|
114
|
-
super();
|
|
115
|
-
|
|
116
|
-
this._checkConfiguration(config);
|
|
117
|
-
this.#config = config;
|
|
118
|
-
this.#localOffer = createDeferredPromise();
|
|
119
|
-
this.#localAnswer = createDeferredPromise();
|
|
120
|
-
this.#dataChannels = new Set();
|
|
121
|
-
this.#canTrickleIceCandidates = null;
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
const peerIdentity = (config as any)?.peerIdentity ?? `peer-${getRandomString(7)}`;
|
|
125
|
-
this.#peerConnection = config?.peerConnection ?? new PeerConnection(peerIdentity,
|
|
126
|
-
{
|
|
127
|
-
...config,
|
|
128
|
-
iceServers:
|
|
129
|
-
config?.iceServers
|
|
130
|
-
?.map((server) => {
|
|
131
|
-
const urls = Array.isArray(server.urls) ? server.urls : [server.urls];
|
|
132
|
-
|
|
133
|
-
return urls.map((url) => {
|
|
134
|
-
if (server.username && server.credential) {
|
|
135
|
-
const [protocol, rest] = url.split(/:(.*)/);
|
|
136
|
-
return `${protocol}:${server.username}:${server.credential}@${rest}`;
|
|
137
|
-
}
|
|
138
|
-
return url;
|
|
139
|
-
});
|
|
140
|
-
})
|
|
141
|
-
.flat() ?? [],
|
|
142
|
-
},
|
|
143
|
-
);
|
|
144
|
-
} catch (error) {
|
|
145
|
-
if (!error || !error.message) throw new exceptions.NotFoundError('Unknown error');
|
|
146
|
-
throw new exceptions.SyntaxError(error.message);
|
|
83
|
+
throw new exceptions.SyntaxError('IceServers urls wrong format');
|
|
84
|
+
|
|
85
|
+
// If this is a turn server check for username and credential
|
|
86
|
+
if ((config.iceServers[i].urls as string[])?.some((url) => url.startsWith('turn'))) {
|
|
87
|
+
if (!config.iceServers[i].username)
|
|
88
|
+
throw new exceptions.InvalidAccessError('IceServers username cannot be null');
|
|
89
|
+
if (!config.iceServers[i].credential)
|
|
90
|
+
throw new exceptions.InvalidAccessError('IceServers username cannot be undefined');
|
|
147
91
|
}
|
|
148
92
|
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
93
|
+
// length of urls can not be 0
|
|
94
|
+
if (config.iceServers[i].urls?.length === 0)
|
|
95
|
+
throw new exceptions.SyntaxError('IceServers urls cannot be empty');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (
|
|
100
|
+
config &&
|
|
101
|
+
config.iceTransportPolicy &&
|
|
102
|
+
config.iceTransportPolicy !== 'all' &&
|
|
103
|
+
config.iceTransportPolicy !== 'relay'
|
|
104
|
+
)
|
|
105
|
+
throw new TypeError('IceTransportPolicy must be either "all" or "relay"');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
setConfiguration(config: globalThis.RTCConfiguration): void {
|
|
109
|
+
this._checkConfiguration(config);
|
|
110
|
+
this.#config = config;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
constructor(config: RTCConfiguration = { iceServers: [], iceTransportPolicy: 'all' }) {
|
|
114
|
+
super();
|
|
115
|
+
|
|
116
|
+
this._checkConfiguration(config);
|
|
117
|
+
this.#config = config;
|
|
118
|
+
this.#localOffer = createDeferredPromise();
|
|
119
|
+
this.#localAnswer = createDeferredPromise();
|
|
120
|
+
this.#dataChannels = new Set();
|
|
121
|
+
this.#canTrickleIceCandidates = null;
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const peerIdentity = (config as any)?.peerIdentity ?? `peer-${getRandomString(7)}`;
|
|
125
|
+
this.#peerConnection =
|
|
126
|
+
config?.peerConnection ??
|
|
127
|
+
new PeerConnection(peerIdentity, {
|
|
128
|
+
...config,
|
|
129
|
+
iceServers:
|
|
130
|
+
config?.iceServers
|
|
131
|
+
?.map((server) => {
|
|
132
|
+
const urls = Array.isArray(server.urls) ? server.urls : [server.urls];
|
|
133
|
+
|
|
134
|
+
return urls.map((url) => {
|
|
135
|
+
if (server.username && server.credential) {
|
|
136
|
+
const [protocol, rest] = url.split(/:(.*)/);
|
|
137
|
+
return `${protocol}:${server.username}:${server.credential}@${rest}`;
|
|
138
|
+
}
|
|
139
|
+
return url;
|
|
140
|
+
});
|
|
141
|
+
})
|
|
142
|
+
.flat() ?? [],
|
|
160
143
|
});
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (!error || !error.message) throw new exceptions.NotFoundError('Unknown error');
|
|
146
|
+
throw new exceptions.SyntaxError(error.message);
|
|
147
|
+
}
|
|
161
148
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
149
|
+
// forward peerConnection events
|
|
150
|
+
this.#peerConnection.onStateChange(() => {
|
|
151
|
+
this.dispatchEvent(new Event('connectionstatechange'));
|
|
152
|
+
});
|
|
165
153
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
this.dispatchEvent(new RTCDataChannelEvent('datachannel', { channel: dc }));
|
|
170
|
-
});
|
|
154
|
+
this.#peerConnection.onIceStateChange(() => {
|
|
155
|
+
this.dispatchEvent(new Event('iceconnectionstatechange'));
|
|
156
|
+
});
|
|
171
157
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
158
|
+
this.#peerConnection.onSignalingStateChange(() => {
|
|
159
|
+
this.dispatchEvent(new Event('signalingstatechange'));
|
|
160
|
+
});
|
|
176
161
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
});
|
|
162
|
+
this.#peerConnection.onGatheringStateChange(() => {
|
|
163
|
+
this.dispatchEvent(new Event('icegatheringstatechange'));
|
|
164
|
+
});
|
|
181
165
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
166
|
+
this.#peerConnection.onDataChannel((channel) => {
|
|
167
|
+
const dc = new RTCDataChannel(channel);
|
|
168
|
+
this.#dataChannels.add(dc);
|
|
169
|
+
this.dispatchEvent(new RTCDataChannelEvent('datachannel', { channel: dc }));
|
|
170
|
+
});
|
|
187
171
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
});
|
|
172
|
+
this.#peerConnection.onLocalDescription((sdp, type) => {
|
|
173
|
+
if (type === 'offer') {
|
|
174
|
+
this.#localOffer.resolve(new RTCSessionDescription({ sdp, type }));
|
|
175
|
+
}
|
|
191
176
|
|
|
192
|
-
|
|
193
|
-
this.
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
this.addEventListener('signalingstatechange', (e) => {
|
|
197
|
-
if (this.onsignalingstatechange) this.onsignalingstatechange(e);
|
|
198
|
-
});
|
|
199
|
-
this.addEventListener('iceconnectionstatechange', (e) => {
|
|
200
|
-
if (this.oniceconnectionstatechange) this.oniceconnectionstatechange(e);
|
|
201
|
-
});
|
|
202
|
-
this.addEventListener('icegatheringstatechange', (e) => {
|
|
203
|
-
if (this.onicegatheringstatechange) this.onicegatheringstatechange(e);
|
|
204
|
-
});
|
|
205
|
-
this.addEventListener('datachannel', (e) => {
|
|
206
|
-
if (this.ondatachannel) this.ondatachannel(e as globalThis.RTCDataChannelEvent);
|
|
207
|
-
});
|
|
208
|
-
this.addEventListener('icecandidate', (e) => {
|
|
209
|
-
if (this.onicecandidate) this.onicecandidate(e as globalThis.RTCPeerConnectionIceEvent);
|
|
210
|
-
});
|
|
177
|
+
if (type === 'answer') {
|
|
178
|
+
this.#localAnswer.resolve(new RTCSessionDescription({ sdp, type }));
|
|
179
|
+
}
|
|
180
|
+
});
|
|
211
181
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
},
|
|
218
|
-
maxMessageSize: (): number => {
|
|
219
|
-
return this.#peerConnection.maxMessageSize();
|
|
220
|
-
},
|
|
221
|
-
localCandidates: (): globalThis.RTCIceCandidate[] => {
|
|
222
|
-
return this.#localCandidates;
|
|
223
|
-
},
|
|
224
|
-
remoteCandidates: (): globalThis.RTCIceCandidate[] => {
|
|
225
|
-
return this.#remoteCandidates;
|
|
226
|
-
},
|
|
227
|
-
selectedCandidatePair: (): { local: SelectedCandidateInfo; remote: SelectedCandidateInfo } | null => {
|
|
228
|
-
return this.#peerConnection.getSelectedCandidatePair();
|
|
229
|
-
},
|
|
230
|
-
},
|
|
231
|
-
});
|
|
232
|
-
}
|
|
182
|
+
this.#peerConnection.onLocalCandidate((candidate, sdpMid) => {
|
|
183
|
+
if (sdpMid === 'unspec') {
|
|
184
|
+
this.#localAnswer.reject(new Error(`Invalid description type ${sdpMid}`));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
233
187
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}
|
|
188
|
+
this.#localCandidates.push(new RTCIceCandidate({ candidate, sdpMid }));
|
|
189
|
+
this.dispatchEvent(new RTCPeerConnectionIceEvent(new RTCIceCandidate({ candidate, sdpMid })));
|
|
190
|
+
});
|
|
237
191
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
192
|
+
// forward events to properties
|
|
193
|
+
this.addEventListener('connectionstatechange', (e) => {
|
|
194
|
+
this.onconnectionstatechange?.(e);
|
|
195
|
+
});
|
|
196
|
+
this.addEventListener('signalingstatechange', (e) => {
|
|
197
|
+
this.onsignalingstatechange?.(e);
|
|
198
|
+
});
|
|
199
|
+
this.addEventListener('iceconnectionstatechange', (e) => {
|
|
200
|
+
this.oniceconnectionstatechange?.(e);
|
|
201
|
+
});
|
|
202
|
+
this.addEventListener('icegatheringstatechange', (e) => {
|
|
203
|
+
this.onicegatheringstatechange?.(e);
|
|
204
|
+
});
|
|
205
|
+
this.addEventListener('datachannel', (e) => {
|
|
206
|
+
this.ondatachannel?.(e as RTCDataChannelEvent);
|
|
207
|
+
});
|
|
208
|
+
this.addEventListener('icecandidate', (e) => {
|
|
209
|
+
this.onicecandidate?.(e as globalThis.RTCPeerConnectionIceEvent);
|
|
210
|
+
});
|
|
211
|
+
this.addEventListener('track', (e) => {
|
|
212
|
+
this.ontrack?.(e as RTCTrackEvent);
|
|
213
|
+
});
|
|
214
|
+
this.addEventListener('negotiationneeded', (e) => {
|
|
215
|
+
this.#announceNegotiation = true;
|
|
216
|
+
this.onnegotiationneeded?.(e);
|
|
217
|
+
});
|
|
241
218
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
if (state == 'completed') state = 'connected';
|
|
247
|
-
return state;
|
|
248
|
-
}
|
|
219
|
+
this.#sctp = new RTCSctpTransport({
|
|
220
|
+
pc: this,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
249
223
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
224
|
+
// Extra FUnctions
|
|
225
|
+
get ext_maxDataChannelId(): number {
|
|
226
|
+
return this.#peerConnection.maxDataChannelId();
|
|
227
|
+
}
|
|
253
228
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
229
|
+
get ext_maxMessageSize(): number {
|
|
230
|
+
return this.#peerConnection.maxMessageSize();
|
|
231
|
+
}
|
|
257
232
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
233
|
+
get ext_localCandidates(): globalThis.RTCIceCandidate[] {
|
|
234
|
+
return this.#localCandidates;
|
|
235
|
+
}
|
|
261
236
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
237
|
+
get ext_remoteCandidates(): globalThis.RTCIceCandidate[] {
|
|
238
|
+
return this.#remoteCandidates;
|
|
239
|
+
}
|
|
265
240
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
241
|
+
selectedCandidatePair(): {
|
|
242
|
+
local: SelectedCandidateInfo;
|
|
243
|
+
remote: SelectedCandidateInfo;
|
|
244
|
+
} | null {
|
|
245
|
+
return this.#peerConnection.getSelectedCandidatePair();
|
|
246
|
+
}
|
|
269
247
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
248
|
+
get canTrickleIceCandidates(): boolean | null {
|
|
249
|
+
return this.#canTrickleIceCandidates;
|
|
250
|
+
}
|
|
273
251
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
252
|
+
get connectionState(): globalThis.RTCPeerConnectionState {
|
|
253
|
+
return this.#peerConnection.state();
|
|
254
|
+
}
|
|
277
255
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
256
|
+
get iceConnectionState(): globalThis.RTCIceConnectionState {
|
|
257
|
+
let state = this.#peerConnection.iceState();
|
|
258
|
+
// libdatachannel uses 'completed' instead of 'connected'
|
|
259
|
+
// see /webrtc/getstats.html
|
|
260
|
+
if (state == 'completed') state = 'connected';
|
|
261
|
+
return state;
|
|
262
|
+
}
|
|
281
263
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
264
|
+
get iceGatheringState(): globalThis.RTCIceGatheringState {
|
|
265
|
+
return this.#peerConnection.gatheringState();
|
|
266
|
+
}
|
|
285
267
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
268
|
+
get currentLocalDescription(): globalThis.RTCSessionDescription {
|
|
269
|
+
return new RTCSessionDescription(this.#peerConnection.localDescription() as any);
|
|
270
|
+
}
|
|
290
271
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
272
|
+
get currentRemoteDescription(): globalThis.RTCSessionDescription {
|
|
273
|
+
return new RTCSessionDescription(this.#peerConnection.remoteDescription() as any);
|
|
274
|
+
}
|
|
294
275
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
276
|
+
get localDescription(): globalThis.RTCSessionDescription {
|
|
277
|
+
return new RTCSessionDescription(this.#peerConnection.localDescription() as any);
|
|
278
|
+
}
|
|
298
279
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
// console.log(candidate.sdpMid);
|
|
303
|
-
throw new exceptions.OperationError('Invalid sdpMid format');
|
|
304
|
-
}
|
|
280
|
+
get pendingLocalDescription(): globalThis.RTCSessionDescription {
|
|
281
|
+
return new RTCSessionDescription(this.#peerConnection.localDescription() as any);
|
|
282
|
+
}
|
|
305
283
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
284
|
+
get pendingRemoteDescription(): globalThis.RTCSessionDescription {
|
|
285
|
+
return new RTCSessionDescription(this.#peerConnection.remoteDescription() as any);
|
|
286
|
+
}
|
|
310
287
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
new RTCIceCandidate({ candidate: candidate.candidate, sdpMid: candidate.sdpMid || '0' }),
|
|
315
|
-
);
|
|
316
|
-
} catch (error) {
|
|
317
|
-
if (!error || !error.message) throw new exceptions.NotFoundError('Unknown error');
|
|
288
|
+
get remoteDescription(): globalThis.RTCSessionDescription {
|
|
289
|
+
return new RTCSessionDescription(this.#peerConnection.remoteDescription() as any);
|
|
290
|
+
}
|
|
318
291
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (error.message.includes('Invalid candidate format')) throw new exceptions.OperationError(error.message);
|
|
292
|
+
get sctp(): globalThis.RTCSctpTransport {
|
|
293
|
+
return this.#sctp;
|
|
294
|
+
}
|
|
323
295
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
296
|
+
get signalingState(): globalThis.RTCSignalingState {
|
|
297
|
+
return this.#peerConnection.signalingState();
|
|
298
|
+
}
|
|
327
299
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
300
|
+
async addIceCandidate(candidate?: globalThis.RTCIceCandidateInit | null): Promise<void> {
|
|
301
|
+
if (!candidate || !candidate.candidate) {
|
|
302
|
+
return;
|
|
331
303
|
}
|
|
332
304
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
throw new DOMException('Not implemented');
|
|
305
|
+
if (candidate.sdpMid === null && candidate.sdpMLineIndex === null) {
|
|
306
|
+
throw new TypeError('sdpMid must be set');
|
|
336
307
|
}
|
|
337
308
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
this.#dataChannels.forEach((channel) => {
|
|
341
|
-
channel.close();
|
|
342
|
-
this.#dataChannelsClosed++;
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
this.#peerConnection.close();
|
|
309
|
+
if (candidate.sdpMid === undefined && candidate.sdpMLineIndex == undefined) {
|
|
310
|
+
throw new TypeError('sdpMid must be set');
|
|
346
311
|
}
|
|
347
312
|
|
|
348
|
-
|
|
349
|
-
|
|
313
|
+
// Reject if sdpMid format is not valid
|
|
314
|
+
// ??
|
|
315
|
+
if (candidate.sdpMid && candidate.sdpMid.length > 3) {
|
|
316
|
+
// console.log(candidate.sdpMid);
|
|
317
|
+
throw new exceptions.OperationError('Invalid sdpMid format');
|
|
350
318
|
}
|
|
351
319
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const dataChannel = new RTCDataChannel(channel, opts);
|
|
356
|
-
|
|
357
|
-
// ensure we can close all channels when shutting down
|
|
358
|
-
this.#dataChannels.add(dataChannel);
|
|
359
|
-
dataChannel.addEventListener('close', () => {
|
|
360
|
-
this.#dataChannels.delete(dataChannel);
|
|
361
|
-
this.#dataChannelsClosed++;
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
return dataChannel;
|
|
320
|
+
// We don't care about sdpMLineIndex, just for test
|
|
321
|
+
if (!candidate.sdpMid && candidate.sdpMLineIndex > 1) {
|
|
322
|
+
throw new exceptions.OperationError('This is only for test case.');
|
|
365
323
|
}
|
|
366
324
|
|
|
367
|
-
|
|
368
|
-
|
|
325
|
+
try {
|
|
326
|
+
this.#peerConnection.addRemoteCandidate(candidate.candidate, candidate.sdpMid ?? '0');
|
|
327
|
+
this.#remoteCandidates.push(
|
|
328
|
+
new RTCIceCandidate({
|
|
329
|
+
candidate: candidate.candidate,
|
|
330
|
+
sdpMid: candidate.sdpMid ?? '0',
|
|
331
|
+
}),
|
|
332
|
+
);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
if (!error || !error.message) throw new exceptions.NotFoundError('Unknown error');
|
|
335
|
+
|
|
336
|
+
// Check error Message if contains specific message
|
|
337
|
+
if (error.message.includes('remote candidate without remote description'))
|
|
338
|
+
throw new exceptions.InvalidStateError(error.message);
|
|
339
|
+
if (error.message.includes('Invalid candidate format'))
|
|
340
|
+
throw new exceptions.OperationError(error.message);
|
|
341
|
+
|
|
342
|
+
throw new exceptions.NotFoundError(error.message);
|
|
369
343
|
}
|
|
344
|
+
}
|
|
370
345
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
346
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
347
|
+
addTrack(_track, ..._streams): globalThis.RTCRtpSender {
|
|
348
|
+
throw new DOMException('Not implemented');
|
|
349
|
+
}
|
|
374
350
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
351
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
352
|
+
addTransceiver(_trackOrKind, _init): globalThis.RTCRtpTransceiver {
|
|
353
|
+
throw new DOMException('Not implemented');
|
|
354
|
+
}
|
|
378
355
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
356
|
+
close(): void {
|
|
357
|
+
this.#peerConnection.close();
|
|
358
|
+
}
|
|
382
359
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const cp = this.#peerConnection?.getSelectedCandidatePair();
|
|
387
|
-
const bytesSent = this.#peerConnection?.bytesSent();
|
|
388
|
-
const bytesReceived = this.#peerConnection?.bytesReceived();
|
|
389
|
-
const rtt = this.#peerConnection?.rtt();
|
|
360
|
+
createAnswer(): Promise<globalThis.RTCSessionDescriptionInit | any> {
|
|
361
|
+
return this.#localAnswer;
|
|
362
|
+
}
|
|
390
363
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
364
|
+
createDataChannel(label: string, opts: globalThis.RTCDataChannelInit = {}): RTCDataChannel {
|
|
365
|
+
const channel = this.#peerConnection.createDataChannel(label, opts);
|
|
366
|
+
const dataChannel = new RTCDataChannel(channel, opts);
|
|
394
367
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
candidateType: cp.local.type,
|
|
402
|
-
ip: cp.local.address,
|
|
403
|
-
port: cp.local.port,
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
const remoteIdRs = getRandomString(8);
|
|
407
|
-
const remoteId = 'RTCIceCandidate_' + remoteIdRs;
|
|
408
|
-
report.set(remoteId, {
|
|
409
|
-
id: remoteId,
|
|
410
|
-
type: 'remote-candidate',
|
|
411
|
-
timestamp: Date.now(),
|
|
412
|
-
candidateType: cp.remote.type,
|
|
413
|
-
ip: cp.remote.address,
|
|
414
|
-
port: cp.remote.port,
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
const candidateId = 'RTCIceCandidatePair_' + localIdRs + '_' + remoteIdRs;
|
|
418
|
-
report.set(candidateId, {
|
|
419
|
-
id: candidateId,
|
|
420
|
-
type: 'candidate-pair',
|
|
421
|
-
timestamp: Date.now(),
|
|
422
|
-
localCandidateId: localId,
|
|
423
|
-
remoteCandidateId: remoteId,
|
|
424
|
-
state: 'succeeded',
|
|
425
|
-
nominated: true,
|
|
426
|
-
writable: true,
|
|
427
|
-
bytesSent: bytesSent,
|
|
428
|
-
bytesReceived: bytesReceived,
|
|
429
|
-
totalRoundTripTime: rtt,
|
|
430
|
-
currentRoundTripTime: rtt,
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
const transportId = 'RTCTransport_0_1';
|
|
434
|
-
report.set(transportId, {
|
|
435
|
-
id: transportId,
|
|
436
|
-
timestamp: Date.now(),
|
|
437
|
-
type: 'transport',
|
|
438
|
-
bytesSent: bytesSent,
|
|
439
|
-
bytesReceived: bytesReceived,
|
|
440
|
-
dtlsState: 'connected',
|
|
441
|
-
selectedCandidatePairId: candidateId,
|
|
442
|
-
selectedCandidatePairChanges: 1,
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
// peer-connection'
|
|
446
|
-
report.set('P', {
|
|
447
|
-
id: 'P',
|
|
448
|
-
type: 'peer-connection',
|
|
449
|
-
timestamp: Date.now(),
|
|
450
|
-
dataChannelsOpened: this.#dataChannels.size,
|
|
451
|
-
dataChannelsClosed: this.#dataChannelsClosed,
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
return resolve(report);
|
|
455
|
-
});
|
|
456
|
-
}
|
|
368
|
+
// ensure we can close all channels when shutting down
|
|
369
|
+
this.#dataChannels.add(dataChannel);
|
|
370
|
+
dataChannel.addEventListener('close', () => {
|
|
371
|
+
this.#dataChannels.delete(dataChannel);
|
|
372
|
+
this.#dataChannelsClosed++;
|
|
373
|
+
});
|
|
457
374
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
375
|
+
if (this.#announceNegotiation == null) {
|
|
376
|
+
this.#announceNegotiation = false;
|
|
377
|
+
this.dispatchEvent(new Event('negotiationneeded'));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return dataChannel;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
createOffer(): Promise<globalThis.RTCSessionDescriptionInit | any> {
|
|
384
|
+
return this.#localOffer;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
getConfiguration(): globalThis.RTCConfiguration {
|
|
388
|
+
return this.#config;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
getReceivers(): globalThis.RTCRtpReceiver[] {
|
|
392
|
+
throw new DOMException('Not implemented');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
getSenders(): globalThis.RTCRtpSender[] {
|
|
396
|
+
throw new DOMException('Not implemented');
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
getStats(): Promise<globalThis.RTCStatsReport> | any {
|
|
400
|
+
return new Promise((resolve) => {
|
|
401
|
+
const report = new Map();
|
|
402
|
+
const cp = this.#peerConnection?.getSelectedCandidatePair();
|
|
403
|
+
const bytesSent = this.#peerConnection?.bytesSent();
|
|
404
|
+
const bytesReceived = this.#peerConnection?.bytesReceived();
|
|
405
|
+
const rtt = this.#peerConnection?.rtt();
|
|
406
|
+
|
|
407
|
+
if (!cp) {
|
|
408
|
+
return resolve(report);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const localIdRs = getRandomString(8);
|
|
412
|
+
const localId = 'RTCIceCandidate_' + localIdRs;
|
|
413
|
+
report.set(localId, {
|
|
414
|
+
id: localId,
|
|
415
|
+
type: 'local-candidate',
|
|
416
|
+
timestamp: Date.now(),
|
|
417
|
+
candidateType: cp.local.type,
|
|
418
|
+
ip: cp.local.address,
|
|
419
|
+
port: cp.local.port,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
const remoteIdRs = getRandomString(8);
|
|
423
|
+
const remoteId = 'RTCIceCandidate_' + remoteIdRs;
|
|
424
|
+
report.set(remoteId, {
|
|
425
|
+
id: remoteId,
|
|
426
|
+
type: 'remote-candidate',
|
|
427
|
+
timestamp: Date.now(),
|
|
428
|
+
candidateType: cp.remote.type,
|
|
429
|
+
ip: cp.remote.address,
|
|
430
|
+
port: cp.remote.port,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
const candidateId = 'RTCIceCandidatePair_' + localIdRs + '_' + remoteIdRs;
|
|
434
|
+
report.set(candidateId, {
|
|
435
|
+
id: candidateId,
|
|
436
|
+
type: 'candidate-pair',
|
|
437
|
+
timestamp: Date.now(),
|
|
438
|
+
localCandidateId: localId,
|
|
439
|
+
remoteCandidateId: remoteId,
|
|
440
|
+
state: 'succeeded',
|
|
441
|
+
nominated: true,
|
|
442
|
+
writable: true,
|
|
443
|
+
bytesSent: bytesSent,
|
|
444
|
+
bytesReceived: bytesReceived,
|
|
445
|
+
totalRoundTripTime: rtt,
|
|
446
|
+
currentRoundTripTime: rtt,
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
const transportId = 'RTCTransport_0_1';
|
|
450
|
+
report.set(transportId, {
|
|
451
|
+
id: transportId,
|
|
452
|
+
timestamp: Date.now(),
|
|
453
|
+
type: 'transport',
|
|
454
|
+
bytesSent: bytesSent,
|
|
455
|
+
bytesReceived: bytesReceived,
|
|
456
|
+
dtlsState: 'connected',
|
|
457
|
+
selectedCandidatePairId: candidateId,
|
|
458
|
+
selectedCandidatePairChanges: 1,
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// peer-connection'
|
|
462
|
+
report.set('P', {
|
|
463
|
+
id: 'P',
|
|
464
|
+
type: 'peer-connection',
|
|
465
|
+
timestamp: Date.now(),
|
|
466
|
+
dataChannelsOpened: this.#dataChannels.size,
|
|
467
|
+
dataChannelsClosed: this.#dataChannelsClosed,
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
return resolve(report);
|
|
471
|
+
});
|
|
472
|
+
}
|
|
461
473
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
474
|
+
getTransceivers(): globalThis.RTCRtpTransceiver[] {
|
|
475
|
+
return []; // throw new DOMException('Not implemented');
|
|
476
|
+
}
|
|
465
477
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
478
|
+
removeTrack(): void {
|
|
479
|
+
throw new DOMException('Not implemented');
|
|
480
|
+
}
|
|
469
481
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
482
|
+
restartIce(): Promise<void> {
|
|
483
|
+
throw new DOMException('Not implemented');
|
|
484
|
+
}
|
|
475
485
|
|
|
476
|
-
|
|
486
|
+
async setLocalDescription(description: globalThis.RTCSessionDescriptionInit): Promise<void> {
|
|
487
|
+
if (description?.type !== 'offer') {
|
|
488
|
+
// any other type causes libdatachannel to throw
|
|
489
|
+
return;
|
|
477
490
|
}
|
|
478
491
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
throw new DOMException('Remote SDP must be set');
|
|
482
|
-
}
|
|
492
|
+
this.#peerConnection.setLocalDescription(description?.type as any);
|
|
493
|
+
}
|
|
483
494
|
|
|
484
|
-
|
|
495
|
+
async setRemoteDescription(description: globalThis.RTCSessionDescriptionInit): Promise<void> {
|
|
496
|
+
if (description.sdp == null) {
|
|
497
|
+
throw new DOMException('Remote SDP must be set');
|
|
485
498
|
}
|
|
499
|
+
|
|
500
|
+
this.#peerConnection.setRemoteDescription(description.sdp, description.type as any);
|
|
501
|
+
}
|
|
486
502
|
}
|
|
487
503
|
|
|
488
504
|
function createDeferredPromise(): any {
|
|
489
|
-
|
|
505
|
+
let resolve: any, reject: any;
|
|
490
506
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
507
|
+
const promise = new Promise(function (_resolve, _reject) {
|
|
508
|
+
resolve = _resolve;
|
|
509
|
+
reject = _reject;
|
|
510
|
+
});
|
|
495
511
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
512
|
+
(promise as any).resolve = resolve;
|
|
513
|
+
(promise as any).reject = reject;
|
|
514
|
+
return promise;
|
|
499
515
|
}
|
|
500
516
|
|
|
501
|
-
function getRandomString(length): string {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
517
|
+
function getRandomString(length: number): string {
|
|
518
|
+
return Math.random()
|
|
519
|
+
.toString(36)
|
|
520
|
+
.substring(2, 2 + length);
|
|
505
521
|
}
|