@gjsify/webrtc 0.1.15
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/lib/esm/get-user-media.js +93 -0
- package/lib/esm/gst-enum-maps.js +88 -0
- package/lib/esm/gst-init.js +34 -0
- package/lib/esm/gst-stats-parser.js +79 -0
- package/lib/esm/gst-utils.js +16 -0
- package/lib/esm/index.js +53 -0
- package/lib/esm/media-device-info.js +23 -0
- package/lib/esm/media-devices.js +147 -0
- package/lib/esm/media-stream-track.js +142 -0
- package/lib/esm/media-stream.js +78 -0
- package/lib/esm/register/data-channel.js +8 -0
- package/lib/esm/register/error.js +8 -0
- package/lib/esm/register/media-devices.js +7 -0
- package/lib/esm/register/media.js +12 -0
- package/lib/esm/register/peer-connection.js +16 -0
- package/lib/esm/register.js +5 -0
- package/lib/esm/rtc-certificate.js +70 -0
- package/lib/esm/rtc-data-channel.js +266 -0
- package/lib/esm/rtc-dtls-transport.js +41 -0
- package/lib/esm/rtc-dtmf-sender.js +109 -0
- package/lib/esm/rtc-error.js +24 -0
- package/lib/esm/rtc-events.js +35 -0
- package/lib/esm/rtc-ice-candidate.js +75 -0
- package/lib/esm/rtc-ice-transport.js +96 -0
- package/lib/esm/rtc-peer-connection.js +855 -0
- package/lib/esm/rtc-rtp-receiver.js +91 -0
- package/lib/esm/rtc-rtp-sender.js +298 -0
- package/lib/esm/rtc-rtp-transceiver.js +97 -0
- package/lib/esm/rtc-sctp-transport.js +40 -0
- package/lib/esm/rtc-session-description.js +57 -0
- package/lib/esm/rtc-stats-report.js +35 -0
- package/lib/esm/rtc-track-event.js +29 -0
- package/lib/esm/rtp-capabilities.js +41 -0
- package/lib/esm/tee-multiplexer.js +62 -0
- package/lib/esm/wpt-helpers.js +122 -0
- package/lib/types/get-user-media.d.ts +14 -0
- package/lib/types/gst-enum-maps.d.ts +10 -0
- package/lib/types/gst-init.d.ts +5 -0
- package/lib/types/gst-stats-parser.d.ts +16 -0
- package/lib/types/gst-utils.d.ts +11 -0
- package/lib/types/index.d.ts +41 -0
- package/lib/types/media-device-info.d.ts +14 -0
- package/lib/types/media-devices.d.ts +12 -0
- package/lib/types/media-stream-track.d.ts +59 -0
- package/lib/types/media-stream.d.ts +28 -0
- package/lib/types/register/data-channel.d.ts +1 -0
- package/lib/types/register/error.d.ts +1 -0
- package/lib/types/register/media-devices.d.ts +1 -0
- package/lib/types/register/media.d.ts +1 -0
- package/lib/types/register/peer-connection.d.ts +1 -0
- package/lib/types/register.d.ts +5 -0
- package/lib/types/register.spec.d.ts +3 -0
- package/lib/types/rtc-certificate.d.ts +23 -0
- package/lib/types/rtc-data-channel.d.ts +64 -0
- package/lib/types/rtc-dtls-transport.d.ts +20 -0
- package/lib/types/rtc-dtmf-sender.d.ts +31 -0
- package/lib/types/rtc-error.d.ts +19 -0
- package/lib/types/rtc-events.d.ts +27 -0
- package/lib/types/rtc-ice-candidate.d.ts +28 -0
- package/lib/types/rtc-ice-transport.d.ts +56 -0
- package/lib/types/rtc-peer-connection.d.ts +165 -0
- package/lib/types/rtc-rtp-receiver.d.ts +45 -0
- package/lib/types/rtc-rtp-sender.d.ts +98 -0
- package/lib/types/rtc-rtp-transceiver.d.ts +20 -0
- package/lib/types/rtc-sctp-transport.d.ts +20 -0
- package/lib/types/rtc-session-description.d.ts +18 -0
- package/lib/types/rtc-stats-report.d.ts +22 -0
- package/lib/types/rtc-track-event.d.ts +18 -0
- package/lib/types/rtp-capabilities.d.ts +3 -0
- package/lib/types/tee-multiplexer.d.ts +25 -0
- package/lib/types/webrtc.spec.d.ts +2 -0
- package/lib/types/wpt-helpers.d.ts +30 -0
- package/lib/types/wpt-media.spec.d.ts +2 -0
- package/lib/types/wpt.spec.d.ts +2 -0
- package/package.json +74 -0
- package/src/get-user-media.ts +131 -0
- package/src/gst-enum-maps.ts +125 -0
- package/src/gst-init.ts +52 -0
- package/src/gst-stats-parser.ts +137 -0
- package/src/gst-utils.ts +41 -0
- package/src/index.ts +104 -0
- package/src/media-device-info.ts +33 -0
- package/src/media-devices.ts +191 -0
- package/src/media-stream-track.ts +159 -0
- package/src/media-stream.ts +96 -0
- package/src/register/data-channel.ts +11 -0
- package/src/register/error.ts +11 -0
- package/src/register/media-devices.ts +10 -0
- package/src/register/media.ts +15 -0
- package/src/register/peer-connection.ts +20 -0
- package/src/register.spec.ts +55 -0
- package/src/register.ts +10 -0
- package/src/rtc-certificate.ts +110 -0
- package/src/rtc-data-channel.ts +284 -0
- package/src/rtc-dtls-transport.ts +48 -0
- package/src/rtc-dtmf-sender.ts +146 -0
- package/src/rtc-error.ts +49 -0
- package/src/rtc-events.ts +64 -0
- package/src/rtc-ice-candidate.ts +115 -0
- package/src/rtc-ice-transport.ts +104 -0
- package/src/rtc-peer-connection.ts +1017 -0
- package/src/rtc-rtp-receiver.ts +122 -0
- package/src/rtc-rtp-sender.ts +444 -0
- package/src/rtc-rtp-transceiver.ts +127 -0
- package/src/rtc-sctp-transport.ts +48 -0
- package/src/rtc-session-description.ts +64 -0
- package/src/rtc-stats-report.ts +39 -0
- package/src/rtc-track-event.ts +45 -0
- package/src/rtp-capabilities.ts +48 -0
- package/src/tee-multiplexer.ts +75 -0
- package/src/test.mts +11 -0
- package/src/webrtc.spec.ts +1186 -0
- package/src/wpt-helpers.ts +156 -0
- package/src/wpt-media.spec.ts +1154 -0
- package/src/wpt.spec.ts +1136 -0
- package/tsconfig.json +36 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import "@gjsify/dom-events/register/event-target";
|
|
2
|
+
import GLib from "gi://GLib?version=2.0";
|
|
3
|
+
class MediaStream extends EventTarget {
|
|
4
|
+
id;
|
|
5
|
+
_tracks = /* @__PURE__ */ new Map();
|
|
6
|
+
_onaddtrack = null;
|
|
7
|
+
_onremovetrack = null;
|
|
8
|
+
constructor(streamOrTracks) {
|
|
9
|
+
super();
|
|
10
|
+
this.id = GLib.uuid_string_random();
|
|
11
|
+
if (streamOrTracks instanceof MediaStream) {
|
|
12
|
+
for (const track of streamOrTracks.getTracks()) {
|
|
13
|
+
this._tracks.set(track.id, track.clone());
|
|
14
|
+
}
|
|
15
|
+
} else if (Array.isArray(streamOrTracks)) {
|
|
16
|
+
for (const track of streamOrTracks) {
|
|
17
|
+
this._tracks.set(track.id, track);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
get active() {
|
|
22
|
+
for (const track of this._tracks.values()) {
|
|
23
|
+
if (track.readyState === "live") return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
get onaddtrack() {
|
|
28
|
+
return this._onaddtrack;
|
|
29
|
+
}
|
|
30
|
+
set onaddtrack(v) {
|
|
31
|
+
this._onaddtrack = v;
|
|
32
|
+
}
|
|
33
|
+
get onremovetrack() {
|
|
34
|
+
return this._onremovetrack;
|
|
35
|
+
}
|
|
36
|
+
set onremovetrack(v) {
|
|
37
|
+
this._onremovetrack = v;
|
|
38
|
+
}
|
|
39
|
+
getTracks() {
|
|
40
|
+
return [...this._tracks.values()];
|
|
41
|
+
}
|
|
42
|
+
getAudioTracks() {
|
|
43
|
+
return this.getTracks().filter((t) => t.kind === "audio");
|
|
44
|
+
}
|
|
45
|
+
getVideoTracks() {
|
|
46
|
+
return this.getTracks().filter((t) => t.kind === "video");
|
|
47
|
+
}
|
|
48
|
+
getTrackById(id) {
|
|
49
|
+
return this._tracks.get(id) ?? null;
|
|
50
|
+
}
|
|
51
|
+
addTrack(track) {
|
|
52
|
+
if (this._tracks.has(track.id)) return;
|
|
53
|
+
this._tracks.set(track.id, track);
|
|
54
|
+
const ev = new MediaStreamTrackEvent("addtrack", { track });
|
|
55
|
+
this._onaddtrack?.call(this, ev);
|
|
56
|
+
this.dispatchEvent(ev);
|
|
57
|
+
}
|
|
58
|
+
removeTrack(track) {
|
|
59
|
+
if (!this._tracks.delete(track.id)) return;
|
|
60
|
+
const ev = new MediaStreamTrackEvent("removetrack", { track });
|
|
61
|
+
this._onremovetrack?.call(this, ev);
|
|
62
|
+
this.dispatchEvent(ev);
|
|
63
|
+
}
|
|
64
|
+
clone() {
|
|
65
|
+
return new MediaStream(this);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
class MediaStreamTrackEvent extends Event {
|
|
69
|
+
track;
|
|
70
|
+
constructor(type, init) {
|
|
71
|
+
super(type, init);
|
|
72
|
+
this.track = init.track;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
MediaStream,
|
|
77
|
+
MediaStreamTrackEvent
|
|
78
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { RTCDataChannel } from "../rtc-data-channel.js";
|
|
2
|
+
import { RTCDataChannelEvent } from "../rtc-events.js";
|
|
3
|
+
if (typeof globalThis.RTCDataChannel === "undefined") {
|
|
4
|
+
globalThis.RTCDataChannel = RTCDataChannel;
|
|
5
|
+
}
|
|
6
|
+
if (typeof globalThis.RTCDataChannelEvent === "undefined") {
|
|
7
|
+
globalThis.RTCDataChannelEvent = RTCDataChannelEvent;
|
|
8
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { RTCError } from "../rtc-error.js";
|
|
2
|
+
import { RTCErrorEvent } from "../rtc-events.js";
|
|
3
|
+
if (typeof globalThis.RTCError === "undefined") {
|
|
4
|
+
globalThis.RTCError = RTCError;
|
|
5
|
+
}
|
|
6
|
+
if (typeof globalThis.RTCErrorEvent === "undefined") {
|
|
7
|
+
globalThis.RTCErrorEvent = RTCErrorEvent;
|
|
8
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { MediaStream } from "../media-stream.js";
|
|
2
|
+
import { MediaStreamTrack } from "../media-stream-track.js";
|
|
3
|
+
import { RTCTrackEvent } from "../rtc-track-event.js";
|
|
4
|
+
if (typeof globalThis.MediaStream === "undefined") {
|
|
5
|
+
globalThis.MediaStream = MediaStream;
|
|
6
|
+
}
|
|
7
|
+
if (typeof globalThis.MediaStreamTrack === "undefined") {
|
|
8
|
+
globalThis.MediaStreamTrack = MediaStreamTrack;
|
|
9
|
+
}
|
|
10
|
+
if (typeof globalThis.RTCTrackEvent === "undefined") {
|
|
11
|
+
globalThis.RTCTrackEvent = RTCTrackEvent;
|
|
12
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { RTCPeerConnection } from "../rtc-peer-connection.js";
|
|
2
|
+
import { RTCSessionDescription } from "../rtc-session-description.js";
|
|
3
|
+
import { RTCIceCandidate } from "../rtc-ice-candidate.js";
|
|
4
|
+
import { RTCPeerConnectionIceEvent } from "../rtc-events.js";
|
|
5
|
+
if (typeof globalThis.RTCPeerConnection === "undefined") {
|
|
6
|
+
globalThis.RTCPeerConnection = RTCPeerConnection;
|
|
7
|
+
}
|
|
8
|
+
if (typeof globalThis.RTCSessionDescription === "undefined") {
|
|
9
|
+
globalThis.RTCSessionDescription = RTCSessionDescription;
|
|
10
|
+
}
|
|
11
|
+
if (typeof globalThis.RTCIceCandidate === "undefined") {
|
|
12
|
+
globalThis.RTCIceCandidate = RTCIceCandidate;
|
|
13
|
+
}
|
|
14
|
+
if (typeof globalThis.RTCPeerConnectionIceEvent === "undefined") {
|
|
15
|
+
globalThis.RTCPeerConnectionIceEvent = RTCPeerConnectionIceEvent;
|
|
16
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import GLib from "gi://GLib?version=2.0";
|
|
2
|
+
const DEFAULT_EXPIRY_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
3
|
+
class RTCCertificate {
|
|
4
|
+
expires;
|
|
5
|
+
// DOMTimeStamp (ms since epoch)
|
|
6
|
+
_fingerprints;
|
|
7
|
+
_algorithm;
|
|
8
|
+
/** @internal — use RTCPeerConnection.generateCertificate() */
|
|
9
|
+
constructor(algorithm, expires, fingerprints) {
|
|
10
|
+
this._algorithm = algorithm;
|
|
11
|
+
this.expires = expires;
|
|
12
|
+
this._fingerprints = fingerprints;
|
|
13
|
+
}
|
|
14
|
+
getFingerprints() {
|
|
15
|
+
return [...this._fingerprints];
|
|
16
|
+
}
|
|
17
|
+
/** @internal — the algorithm name for debugging/inspection */
|
|
18
|
+
get _algorithmName() {
|
|
19
|
+
return this._algorithm;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function generateCertificate(keygenAlgorithm) {
|
|
23
|
+
let name;
|
|
24
|
+
if (typeof keygenAlgorithm === "string") {
|
|
25
|
+
name = keygenAlgorithm.toLowerCase();
|
|
26
|
+
} else if (keygenAlgorithm && typeof keygenAlgorithm === "object" && typeof keygenAlgorithm.name === "string") {
|
|
27
|
+
name = keygenAlgorithm.name.toLowerCase();
|
|
28
|
+
} else {
|
|
29
|
+
throw new DOMException(
|
|
30
|
+
"generateCertificate: algorithm must have a name property",
|
|
31
|
+
"NotSupportedError"
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
if (name === "ecdsa") {
|
|
35
|
+
const curve = keygenAlgorithm.namedCurve;
|
|
36
|
+
if (curve && curve !== "P-256") {
|
|
37
|
+
throw new DOMException(
|
|
38
|
+
`generateCertificate: unsupported ECDSA curve '${curve}'`,
|
|
39
|
+
"NotSupportedError"
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
} else if (name === "rsassa-pkcs1-v1_5") {
|
|
43
|
+
const hash = keygenAlgorithm.hash;
|
|
44
|
+
const hashName = typeof hash === "string" ? hash : hash?.name;
|
|
45
|
+
if (hashName && hashName.toUpperCase() === "SHA-1") {
|
|
46
|
+
throw new DOMException(
|
|
47
|
+
"generateCertificate: SHA-1 is not supported for RSA certificates",
|
|
48
|
+
"NotSupportedError"
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
throw new DOMException(
|
|
53
|
+
`generateCertificate: unsupported algorithm '${name}'`,
|
|
54
|
+
"NotSupportedError"
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
const uuid = GLib.uuid_string_random();
|
|
58
|
+
const checksum = GLib.Checksum.new(GLib.ChecksumType.SHA256);
|
|
59
|
+
checksum.update(new TextEncoder().encode(uuid + Date.now()));
|
|
60
|
+
const fingerprintHex = checksum.get_string();
|
|
61
|
+
const formatted = fingerprintHex.slice(0, 64).match(/.{2}/g).join(":").toUpperCase();
|
|
62
|
+
const expires = Date.now() + DEFAULT_EXPIRY_MS;
|
|
63
|
+
return new RTCCertificate(name, expires, [
|
|
64
|
+
{ algorithm: "sha-256", value: formatted }
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
67
|
+
export {
|
|
68
|
+
RTCCertificate,
|
|
69
|
+
generateCertificate
|
|
70
|
+
};
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import GLib from "gi://GLib?version=2.0";
|
|
2
|
+
import { DataChannelBridge } from "@gjsify/webrtc-native";
|
|
3
|
+
import { RTCError } from "./rtc-error.js";
|
|
4
|
+
import { RTCErrorEvent } from "./rtc-events.js";
|
|
5
|
+
const STATE_MAP = {
|
|
6
|
+
1: "connecting",
|
|
7
|
+
2: "open",
|
|
8
|
+
3: "closing",
|
|
9
|
+
4: "closed"
|
|
10
|
+
};
|
|
11
|
+
function toGBytes(buffer) {
|
|
12
|
+
let view;
|
|
13
|
+
if (ArrayBuffer.isView(buffer)) {
|
|
14
|
+
view = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
15
|
+
} else {
|
|
16
|
+
view = new Uint8Array(buffer);
|
|
17
|
+
}
|
|
18
|
+
return new GLib.Bytes(view);
|
|
19
|
+
}
|
|
20
|
+
function bytesToArrayBuffer(bytes) {
|
|
21
|
+
const arr = bytes.toArray?.();
|
|
22
|
+
if (arr instanceof Uint8Array) {
|
|
23
|
+
return arr.buffer.slice(arr.byteOffset, arr.byteOffset + arr.byteLength);
|
|
24
|
+
}
|
|
25
|
+
const data = bytes.get_data?.();
|
|
26
|
+
if (data instanceof Uint8Array) {
|
|
27
|
+
return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
28
|
+
}
|
|
29
|
+
return new ArrayBuffer(0);
|
|
30
|
+
}
|
|
31
|
+
class RTCDataChannel extends EventTarget {
|
|
32
|
+
_native;
|
|
33
|
+
_bridge;
|
|
34
|
+
_binaryType = "arraybuffer";
|
|
35
|
+
_bufferedAmount = 0;
|
|
36
|
+
_closed = false;
|
|
37
|
+
// `on<event>` attribute handlers — W3C requires both addEventListener and on*.
|
|
38
|
+
_onopen = null;
|
|
39
|
+
_onclose = null;
|
|
40
|
+
_onerror = null;
|
|
41
|
+
_onmessage = null;
|
|
42
|
+
_onbufferedamountlow = null;
|
|
43
|
+
_onclosing = null;
|
|
44
|
+
/**
|
|
45
|
+
* @internal
|
|
46
|
+
* Accepts either a raw GstWebRTCDataChannel (for locally-created channels)
|
|
47
|
+
* or a pre-made DataChannelBridge (for remotely-originated channels that
|
|
48
|
+
* the WebrtcbinBridge already wrapped on the streaming thread to avoid
|
|
49
|
+
* missing early messages).
|
|
50
|
+
*/
|
|
51
|
+
constructor(source) {
|
|
52
|
+
super();
|
|
53
|
+
if (source.channel !== void 0 && source.dispose_bridge) {
|
|
54
|
+
this._bridge = source;
|
|
55
|
+
this._native = this._bridge.channel;
|
|
56
|
+
} else {
|
|
57
|
+
this._native = source;
|
|
58
|
+
this._bridge = new DataChannelBridge({ channel: this._native });
|
|
59
|
+
}
|
|
60
|
+
this._bridge.connect("opened", () => this._handleOpen());
|
|
61
|
+
this._bridge.connect("closed", () => this._handleClose());
|
|
62
|
+
this._bridge.connect("error-occurred", (_b, message) => this._handleError(message));
|
|
63
|
+
this._bridge.connect("message-string", (_b, data) => this._handleString(data));
|
|
64
|
+
this._bridge.connect("message-data", (_b, data) => this._handleData(data));
|
|
65
|
+
this._bridge.connect("buffered-amount-low", () => this._handleBufferedAmountLow());
|
|
66
|
+
this._bridge.connect("ready-state-changed", () => this._handleReadyStateChange());
|
|
67
|
+
}
|
|
68
|
+
// ---- Properties --------------------------------------------------------
|
|
69
|
+
get label() {
|
|
70
|
+
return this._native.label;
|
|
71
|
+
}
|
|
72
|
+
get ordered() {
|
|
73
|
+
return this._native.ordered;
|
|
74
|
+
}
|
|
75
|
+
get protocol() {
|
|
76
|
+
return this._native.protocol;
|
|
77
|
+
}
|
|
78
|
+
get negotiated() {
|
|
79
|
+
return this._native.negotiated;
|
|
80
|
+
}
|
|
81
|
+
get id() {
|
|
82
|
+
return this._native.id >= 0 ? this._native.id : null;
|
|
83
|
+
}
|
|
84
|
+
get maxPacketLifeTime() {
|
|
85
|
+
const v = this._native.max_packet_lifetime;
|
|
86
|
+
return v >= 0 ? v : null;
|
|
87
|
+
}
|
|
88
|
+
get maxRetransmits() {
|
|
89
|
+
const v = this._native.max_retransmits;
|
|
90
|
+
return v >= 0 ? v : null;
|
|
91
|
+
}
|
|
92
|
+
get readyState() {
|
|
93
|
+
if (this._closed) return "closed";
|
|
94
|
+
return STATE_MAP[this._native.ready_state] ?? "connecting";
|
|
95
|
+
}
|
|
96
|
+
get bufferedAmount() {
|
|
97
|
+
try {
|
|
98
|
+
return Number(this._native.buffered_amount) || this._bufferedAmount;
|
|
99
|
+
} catch {
|
|
100
|
+
return this._bufferedAmount;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
get bufferedAmountLowThreshold() {
|
|
104
|
+
return Number(this._native.buffered_amount_low_threshold) || 0;
|
|
105
|
+
}
|
|
106
|
+
set bufferedAmountLowThreshold(v) {
|
|
107
|
+
this._native.buffered_amount_low_threshold = v;
|
|
108
|
+
}
|
|
109
|
+
get binaryType() {
|
|
110
|
+
return this._binaryType;
|
|
111
|
+
}
|
|
112
|
+
set binaryType(v) {
|
|
113
|
+
if (v !== "arraybuffer" && v !== "blob") return;
|
|
114
|
+
if (v === "blob" && typeof globalThis.Blob === "undefined") {
|
|
115
|
+
const DOMExc = globalThis.DOMException;
|
|
116
|
+
const msg = `binaryType 'blob' requires globalThis.Blob. Import '@gjsify/buffer/register' to provide it.`;
|
|
117
|
+
throw DOMExc ? new DOMExc(msg, "NotSupportedError") : new Error(msg);
|
|
118
|
+
}
|
|
119
|
+
this._binaryType = v;
|
|
120
|
+
}
|
|
121
|
+
// ---- on<event> attribute accessors -------------------------------------
|
|
122
|
+
get onopen() {
|
|
123
|
+
return this._onopen;
|
|
124
|
+
}
|
|
125
|
+
set onopen(h) {
|
|
126
|
+
this._onopen = h;
|
|
127
|
+
}
|
|
128
|
+
get onclose() {
|
|
129
|
+
return this._onclose;
|
|
130
|
+
}
|
|
131
|
+
set onclose(h) {
|
|
132
|
+
this._onclose = h;
|
|
133
|
+
}
|
|
134
|
+
get onclosing() {
|
|
135
|
+
return this._onclosing;
|
|
136
|
+
}
|
|
137
|
+
set onclosing(h) {
|
|
138
|
+
this._onclosing = h;
|
|
139
|
+
}
|
|
140
|
+
get onerror() {
|
|
141
|
+
return this._onerror;
|
|
142
|
+
}
|
|
143
|
+
set onerror(h) {
|
|
144
|
+
this._onerror = h;
|
|
145
|
+
}
|
|
146
|
+
get onmessage() {
|
|
147
|
+
return this._onmessage;
|
|
148
|
+
}
|
|
149
|
+
set onmessage(h) {
|
|
150
|
+
this._onmessage = h;
|
|
151
|
+
}
|
|
152
|
+
get onbufferedamountlow() {
|
|
153
|
+
return this._onbufferedamountlow;
|
|
154
|
+
}
|
|
155
|
+
set onbufferedamountlow(h) {
|
|
156
|
+
this._onbufferedamountlow = h;
|
|
157
|
+
}
|
|
158
|
+
// ---- Methods -----------------------------------------------------------
|
|
159
|
+
send(data) {
|
|
160
|
+
const state = this.readyState;
|
|
161
|
+
if (state !== "open") {
|
|
162
|
+
const DOMExc = globalThis.DOMException;
|
|
163
|
+
const msg = `RTCDataChannel.send: readyState is '${state}', expected 'open'`;
|
|
164
|
+
throw DOMExc ? new DOMExc(msg, "InvalidStateError") : new Error(msg);
|
|
165
|
+
}
|
|
166
|
+
if (typeof data === "string") {
|
|
167
|
+
this._native.send_string(data);
|
|
168
|
+
this._bufferedAmount += new TextEncoder().encode(data).byteLength;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (typeof globalThis.Blob !== "undefined" && data instanceof globalThis.Blob) {
|
|
172
|
+
const blob = data;
|
|
173
|
+
blob.arrayBuffer().then((buf) => {
|
|
174
|
+
try {
|
|
175
|
+
this._native.send_data(toGBytes(buf));
|
|
176
|
+
this._bufferedAmount += buf.byteLength;
|
|
177
|
+
} catch {
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (ArrayBuffer.isView(data)) {
|
|
183
|
+
const bytes = toGBytes(data);
|
|
184
|
+
this._native.send_data(bytes);
|
|
185
|
+
this._bufferedAmount += data.byteLength;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (data instanceof ArrayBuffer) {
|
|
189
|
+
const bytes = toGBytes(data);
|
|
190
|
+
this._native.send_data(bytes);
|
|
191
|
+
this._bufferedAmount += data.byteLength;
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
throw new TypeError("RTCDataChannel.send: unsupported data type");
|
|
195
|
+
}
|
|
196
|
+
close() {
|
|
197
|
+
if (this._closed) return;
|
|
198
|
+
try {
|
|
199
|
+
this._native.close();
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
this._disconnectSignals();
|
|
203
|
+
this._closed = true;
|
|
204
|
+
}
|
|
205
|
+
/** @internal */
|
|
206
|
+
_disconnectSignals() {
|
|
207
|
+
try {
|
|
208
|
+
this._bridge.dispose_bridge();
|
|
209
|
+
} catch {
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// ---- Signal → event translators ---------------------------------------
|
|
213
|
+
// Already running on the main context (DataChannelBridge did the hop).
|
|
214
|
+
_handleOpen() {
|
|
215
|
+
const ev = new Event("open");
|
|
216
|
+
this._onopen?.call(this, ev);
|
|
217
|
+
this.dispatchEvent(ev);
|
|
218
|
+
}
|
|
219
|
+
_handleClose() {
|
|
220
|
+
this._closed = true;
|
|
221
|
+
const ev = new Event("close");
|
|
222
|
+
this._onclose?.call(this, ev);
|
|
223
|
+
this.dispatchEvent(ev);
|
|
224
|
+
}
|
|
225
|
+
_handleError(message) {
|
|
226
|
+
const rtcErr = new RTCError(
|
|
227
|
+
{ errorDetail: "data-channel-failure" },
|
|
228
|
+
message || "RTCDataChannel error"
|
|
229
|
+
);
|
|
230
|
+
const ev = new RTCErrorEvent("error", { error: rtcErr });
|
|
231
|
+
this._onerror?.call(this, ev);
|
|
232
|
+
this.dispatchEvent(ev);
|
|
233
|
+
}
|
|
234
|
+
_handleString(data) {
|
|
235
|
+
const ev = new MessageEvent("message", { data });
|
|
236
|
+
this._onmessage?.call(this, ev);
|
|
237
|
+
this.dispatchEvent(ev);
|
|
238
|
+
}
|
|
239
|
+
_handleData(bytes) {
|
|
240
|
+
if (!bytes) return;
|
|
241
|
+
const buf = bytesToArrayBuffer(bytes);
|
|
242
|
+
let data = buf;
|
|
243
|
+
if (this._binaryType === "blob" && typeof globalThis.Blob !== "undefined") {
|
|
244
|
+
data = new globalThis.Blob([buf]);
|
|
245
|
+
}
|
|
246
|
+
const ev = new MessageEvent("message", { data });
|
|
247
|
+
this._onmessage?.call(this, ev);
|
|
248
|
+
this.dispatchEvent(ev);
|
|
249
|
+
}
|
|
250
|
+
_handleBufferedAmountLow() {
|
|
251
|
+
this._bufferedAmount = Number(this._native.buffered_amount) || 0;
|
|
252
|
+
const ev = new Event("bufferedamountlow");
|
|
253
|
+
this._onbufferedamountlow?.call(this, ev);
|
|
254
|
+
this.dispatchEvent(ev);
|
|
255
|
+
}
|
|
256
|
+
_handleReadyStateChange() {
|
|
257
|
+
if (this.readyState === "closing") {
|
|
258
|
+
const ev = new Event("closing");
|
|
259
|
+
this._onclosing?.call(this, ev);
|
|
260
|
+
this.dispatchEvent(ev);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
export {
|
|
265
|
+
RTCDataChannel
|
|
266
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import "@gjsify/dom-events/register/event-target";
|
|
2
|
+
class RTCDtlsTransport extends EventTarget {
|
|
3
|
+
iceTransport;
|
|
4
|
+
_state = "new";
|
|
5
|
+
_onstatechange = null;
|
|
6
|
+
_onerror = null;
|
|
7
|
+
constructor(iceTransport) {
|
|
8
|
+
super();
|
|
9
|
+
this.iceTransport = iceTransport;
|
|
10
|
+
}
|
|
11
|
+
get state() {
|
|
12
|
+
return this._state;
|
|
13
|
+
}
|
|
14
|
+
get onstatechange() {
|
|
15
|
+
return this._onstatechange;
|
|
16
|
+
}
|
|
17
|
+
set onstatechange(v) {
|
|
18
|
+
this._onstatechange = v;
|
|
19
|
+
}
|
|
20
|
+
get onerror() {
|
|
21
|
+
return this._onerror;
|
|
22
|
+
}
|
|
23
|
+
set onerror(v) {
|
|
24
|
+
this._onerror = v;
|
|
25
|
+
}
|
|
26
|
+
getRemoteCertificates() {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
// ---- Internal setters (called by RTCPeerConnection) ---------------------
|
|
30
|
+
/** @internal */
|
|
31
|
+
_setState(state) {
|
|
32
|
+
if (this._state === state) return;
|
|
33
|
+
this._state = state;
|
|
34
|
+
const ev = new Event("statechange");
|
|
35
|
+
this._onstatechange?.call(this, ev);
|
|
36
|
+
this.dispatchEvent(ev);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
RTCDtlsTransport
|
|
41
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import "@gjsify/dom-events/register/event-target";
|
|
2
|
+
const VALID_DTMF_CHARS = new Set("0123456789ABCDabcd#*,");
|
|
3
|
+
const MIN_DURATION = 40;
|
|
4
|
+
const MAX_DURATION = 6e3;
|
|
5
|
+
const DEFAULT_DURATION = 100;
|
|
6
|
+
const MIN_INTER_TONE_GAP = 30;
|
|
7
|
+
const DEFAULT_INTER_TONE_GAP = 70;
|
|
8
|
+
const COMMA_DELAY = 2e3;
|
|
9
|
+
class RTCDTMFToneChangeEvent extends Event {
|
|
10
|
+
tone;
|
|
11
|
+
constructor(type, init = {}) {
|
|
12
|
+
super(type, init);
|
|
13
|
+
this.tone = init.tone ?? "";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
class RTCDTMFSender extends EventTarget {
|
|
17
|
+
_toneBuffer = "";
|
|
18
|
+
_duration = DEFAULT_DURATION;
|
|
19
|
+
_interToneGap = DEFAULT_INTER_TONE_GAP;
|
|
20
|
+
_timerId = null;
|
|
21
|
+
_canInsert = true;
|
|
22
|
+
_ontonechange = null;
|
|
23
|
+
/** @internal — back-references set by RTCRtpSender */
|
|
24
|
+
_isStopped = () => false;
|
|
25
|
+
_getCurrentDirection = () => null;
|
|
26
|
+
get toneBuffer() {
|
|
27
|
+
return this._toneBuffer;
|
|
28
|
+
}
|
|
29
|
+
get canInsertDTMF() {
|
|
30
|
+
return this._canInsert;
|
|
31
|
+
}
|
|
32
|
+
get ontonechange() {
|
|
33
|
+
return this._ontonechange;
|
|
34
|
+
}
|
|
35
|
+
set ontonechange(v) {
|
|
36
|
+
this._ontonechange = v;
|
|
37
|
+
}
|
|
38
|
+
insertDTMF(tones, duration, interToneGap) {
|
|
39
|
+
if (this._isStopped()) {
|
|
40
|
+
throw new DOMException(
|
|
41
|
+
"Failed to execute 'insertDTMF': The associated transceiver is stopped",
|
|
42
|
+
"InvalidStateError"
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const dir = this._getCurrentDirection();
|
|
46
|
+
if (dir === "recvonly" || dir === "inactive") {
|
|
47
|
+
throw new DOMException(
|
|
48
|
+
"Failed to execute 'insertDTMF': The associated transceiver direction does not allow sending",
|
|
49
|
+
"InvalidStateError"
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
for (const ch of tones) {
|
|
53
|
+
if (!VALID_DTMF_CHARS.has(ch)) {
|
|
54
|
+
throw new DOMException(
|
|
55
|
+
`Failed to execute 'insertDTMF': Invalid DTMF character '${ch}'`,
|
|
56
|
+
"InvalidCharacterError"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
tones = tones.replace(/[a-d]/g, (c) => c.toUpperCase());
|
|
61
|
+
const d = duration ?? DEFAULT_DURATION;
|
|
62
|
+
this._duration = Math.max(MIN_DURATION, Math.min(MAX_DURATION, d));
|
|
63
|
+
const g = interToneGap ?? DEFAULT_INTER_TONE_GAP;
|
|
64
|
+
this._interToneGap = Math.max(MIN_INTER_TONE_GAP, g);
|
|
65
|
+
this._toneBuffer = tones;
|
|
66
|
+
if (this._timerId !== null) {
|
|
67
|
+
clearTimeout(this._timerId);
|
|
68
|
+
this._timerId = null;
|
|
69
|
+
}
|
|
70
|
+
if (tones.length > 0) {
|
|
71
|
+
this._scheduleNextTone(0);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
_scheduleNextTone(delay) {
|
|
75
|
+
this._timerId = setTimeout(() => {
|
|
76
|
+
this._timerId = null;
|
|
77
|
+
this._playNextTone();
|
|
78
|
+
}, delay);
|
|
79
|
+
}
|
|
80
|
+
_playNextTone() {
|
|
81
|
+
if (this._toneBuffer.length === 0) {
|
|
82
|
+
this._fireToneChange("");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const tone = this._toneBuffer[0];
|
|
86
|
+
this._toneBuffer = this._toneBuffer.slice(1);
|
|
87
|
+
this._fireToneChange(tone);
|
|
88
|
+
const delay = tone === "," ? COMMA_DELAY : this._duration + this._interToneGap;
|
|
89
|
+
this._scheduleNextTone(delay);
|
|
90
|
+
}
|
|
91
|
+
_fireToneChange(tone) {
|
|
92
|
+
const ev = new RTCDTMFToneChangeEvent("tonechange", { tone });
|
|
93
|
+
this._ontonechange?.call(this, ev);
|
|
94
|
+
this.dispatchEvent(ev);
|
|
95
|
+
}
|
|
96
|
+
/** @internal — called by RTCRtpSender on cleanup */
|
|
97
|
+
_stop() {
|
|
98
|
+
if (this._timerId !== null) {
|
|
99
|
+
clearTimeout(this._timerId);
|
|
100
|
+
this._timerId = null;
|
|
101
|
+
}
|
|
102
|
+
this._toneBuffer = "";
|
|
103
|
+
this._canInsert = false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export {
|
|
107
|
+
RTCDTMFSender,
|
|
108
|
+
RTCDTMFToneChangeEvent
|
|
109
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import "@gjsify/dom-exception/register";
|
|
2
|
+
class RTCError extends DOMException {
|
|
3
|
+
errorDetail;
|
|
4
|
+
sdpLineNumber;
|
|
5
|
+
sctpCauseCode;
|
|
6
|
+
receivedAlert;
|
|
7
|
+
sentAlert;
|
|
8
|
+
httpRequestStatusCode;
|
|
9
|
+
constructor(init, message) {
|
|
10
|
+
super(message ?? init.errorDetail, "OperationError");
|
|
11
|
+
if (!init || !init.errorDetail) {
|
|
12
|
+
throw new TypeError("RTCError: errorDetail is required");
|
|
13
|
+
}
|
|
14
|
+
this.errorDetail = init.errorDetail;
|
|
15
|
+
this.sdpLineNumber = init.sdpLineNumber ?? null;
|
|
16
|
+
this.sctpCauseCode = init.sctpCauseCode ?? null;
|
|
17
|
+
this.receivedAlert = init.receivedAlert ?? null;
|
|
18
|
+
this.sentAlert = init.sentAlert ?? null;
|
|
19
|
+
this.httpRequestStatusCode = init.httpRequestStatusCode ?? null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
RTCError
|
|
24
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import "@gjsify/dom-events/register/event-target";
|
|
2
|
+
class RTCPeerConnectionIceEvent extends Event {
|
|
3
|
+
candidate;
|
|
4
|
+
url;
|
|
5
|
+
constructor(type, init = {}) {
|
|
6
|
+
super(type, init);
|
|
7
|
+
this.candidate = init.candidate ?? null;
|
|
8
|
+
this.url = init.url ?? null;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
class RTCDataChannelEvent extends Event {
|
|
12
|
+
channel;
|
|
13
|
+
constructor(type, init) {
|
|
14
|
+
super(type, init);
|
|
15
|
+
if (!init || !init.channel) {
|
|
16
|
+
throw new TypeError("RTCDataChannelEvent requires a `channel` member");
|
|
17
|
+
}
|
|
18
|
+
this.channel = init.channel;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
class RTCErrorEvent extends Event {
|
|
22
|
+
error;
|
|
23
|
+
constructor(type, init) {
|
|
24
|
+
super(type, init);
|
|
25
|
+
if (!init || !init.error) {
|
|
26
|
+
throw new TypeError("RTCErrorEvent requires an `error` member");
|
|
27
|
+
}
|
|
28
|
+
this.error = init.error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
RTCDataChannelEvent,
|
|
33
|
+
RTCErrorEvent,
|
|
34
|
+
RTCPeerConnectionIceEvent
|
|
35
|
+
};
|