@ohos-ports/agora-rtc-sdk-ng 4.24.8-beta.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/AgoraRTC_N-production.esm.mjs +6 -0
- package/AgoraRTC_N-production.full.js +6 -0
- package/AgoraRTC_N-production.js +6 -0
- package/README-zh_CN.md +65 -0
- package/README.md +66 -0
- package/browser-shim.cjs +424 -0
- package/esm/AgoraRTC_N-production.esm-bundler.mjs +5 -0
- package/esm/package.json +8 -0
- package/esm/rtc-sdk_en.esm.d.ts +3138 -0
- package/node-entry.cjs +12 -0
- package/package.json +101 -0
- package/rtc-sdk_cn.d.ts +6368 -0
- package/rtc-sdk_en.d.ts +6096 -0
- package/services/ai-audio-mode/index.d.ts +97 -0
- package/services/ai-audio-mode/index.js +5 -0
- package/services/channel-media-relay/index.d.ts +132 -0
- package/services/channel-media-relay/index.js +5 -0
- package/services/content-inspect/index.d.ts +79 -0
- package/services/content-inspect/index.js +5 -0
- package/services/data-stream/index.d.ts +12 -0
- package/services/data-stream/index.js +5 -0
- package/services/datachannel/index.d.ts +679 -0
- package/services/datachannel/index.js +5 -0
- package/services/image-moderation/index.d.ts +88 -0
- package/services/image-moderation/index.js +5 -0
- package/services/intercept-frame/index.d.ts +19 -0
- package/services/intercept-frame/index.js +5 -0
- package/services/live-streaming/index.d.ts +267 -0
- package/services/live-streaming/index.js +5 -0
- package/services/p2p-channel/index.d.ts +1021 -0
- package/services/p2p-channel/index.js +5 -0
- package/services/planb-connection/index.d.ts +236 -0
- package/services/planb-connection/index.js +5 -0
package/README-zh_CN.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# AgoraRTC SDK (Version 4.x)
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | 简体中文
|
|
4
|
+
|
|
5
|
+
## 开始
|
|
6
|
+
|
|
7
|
+
### 安装
|
|
8
|
+
|
|
9
|
+
使用 `npm`, `pnpm` 或 `yarn`
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# 使用 npm
|
|
13
|
+
npm i agora-rtc-sdk-ng
|
|
14
|
+
# 或者使用 pnpm
|
|
15
|
+
pnpm add agora-rtc-sdk-ng
|
|
16
|
+
# 或者使用 yarn
|
|
17
|
+
yarn add agora-rtc-sdk-ng
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
我们也提供了 CDN。SDK 会在全局导出 `AgoraRTC`。
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<!-- 默认 UMD,不含 AI QoS -->
|
|
24
|
+
<script src="https://download.agora.io/sdk/release/AgoraRTC_N-4.24.8.js"></script>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
CDN 使用 AI QoS 时只引入 full 包,AI 已注册,不必再挂扩展脚本:
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<script src="https://download.agora.io/sdk/release/AgoraRTC_N-4.24.8-full.js"></script>
|
|
31
|
+
<script>
|
|
32
|
+
const client = AgoraRTC.createClient({ mode: "live", codec: "vp8", aiClientMode: true });
|
|
33
|
+
await client.join(appId, channel, token, uid);
|
|
34
|
+
</script>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
npm 的 UMD 同样拆分。默认入口不含 AI QoS;完整包走 `agora-rtc-sdk-ng/full`:
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
import AgoraRTC from "agora-rtc-sdk-ng/full";
|
|
41
|
+
|
|
42
|
+
const client = AgoraRTC.createClient({ mode: "live", codec: "vp8", aiClientMode: true });
|
|
43
|
+
await client.join(appId, channel, token, uid);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
ESM 逻辑不变:主 ESM + 可选 service:
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
import AgoraRTC from "agora-rtc-sdk-ng/esm";
|
|
50
|
+
import { AiAudioModeService } from "agora-rtc-sdk-ng/services/ai-audio-mode";
|
|
51
|
+
|
|
52
|
+
AgoraRTC.use(AiAudioModeService);
|
|
53
|
+
const client = AgoraRTC.createClient({ mode: "live", codec: "vp8", aiClientMode: true });
|
|
54
|
+
await client.join(appId, channel, token, uid);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 快速开始
|
|
58
|
+
|
|
59
|
+
我们提供了 `React`, `Vue`, `Svelte` 的 Demo, 访问地址 [Quick Start Demo](https://github.com/AgoraIO/agora-rtc-web/blob/main/projects).
|
|
60
|
+
|
|
61
|
+
详细可参考文档 [api-ref.agora.io](https://doc.shengwang.cn/api-ref/rtc/javascript/overview).
|
|
62
|
+
|
|
63
|
+
## 变更记录
|
|
64
|
+
|
|
65
|
+
变更记录访问地址为 [Release Notes](https://doc.shengwang.cn/doc/rtc/javascript/overview/release-notes).
|
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# AgoraRTC SDK (Version 4.x)
|
|
2
|
+
|
|
3
|
+
English | [简体中文](https://github.com/AgoraIO/agora-rtc-web/blob/main/README-zh_CN.md)
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
### Install
|
|
8
|
+
|
|
9
|
+
Use `npm`, `pnpm` or `yarn`
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# with npm
|
|
13
|
+
npm i agora-rtc-sdk-ng
|
|
14
|
+
# or with pnpm
|
|
15
|
+
pnpm add agora-rtc-sdk-ng
|
|
16
|
+
# or with yarn
|
|
17
|
+
yarn add agora-rtc-sdk-ng
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
CDN is also provided. The SDK exports a global `AgoraRTC` object.
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<!-- default UMD, no AI QoS -->
|
|
24
|
+
<script src="https://download.agora.io/sdk/release/AgoraRTC_N-4.24.8.js"></script>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
To use AI QoS from CDN, load the full UMD instead. AI is already registered; only this one script is needed:
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<script src="https://download.agora.io/sdk/release/AgoraRTC_N-4.24.8-full.js"></script>
|
|
31
|
+
<script>
|
|
32
|
+
const client = AgoraRTC.createClient({ mode: "live", codec: "vp8", aiClientMode: true });
|
|
33
|
+
await client.join(appId, channel, token, uid);
|
|
34
|
+
</script>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
npm UMD is the same split. Default has no AI QoS; `agora-rtc-sdk-ng/full` is the complete build:
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
import AgoraRTC from "agora-rtc-sdk-ng/full";
|
|
41
|
+
|
|
42
|
+
const client = AgoraRTC.createClient({ mode: "live", codec: "vp8", aiClientMode: true });
|
|
43
|
+
await client.join(appId, channel, token, uid);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
ESM is unchanged: import the main ESM, then the optional service:
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
import AgoraRTC from "agora-rtc-sdk-ng/esm";
|
|
50
|
+
import { AiAudioModeService } from "agora-rtc-sdk-ng/services/ai-audio-mode";
|
|
51
|
+
|
|
52
|
+
AgoraRTC.use(AiAudioModeService);
|
|
53
|
+
const client = AgoraRTC.createClient({ mode: "live", codec: "vp8", aiClientMode: true });
|
|
54
|
+
await client.join(appId, channel, token, uid);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Quick start
|
|
58
|
+
|
|
59
|
+
`React`, `Vue`, `Svelte` quick start demo are provided, please check at [Quick Start Demo](https://github.com/AgoraIO/agora-rtc-web/blob/main/projects).
|
|
60
|
+
We also have [More Demos](https://github.com/AgoraIO/API-Examples-Web/tree/main/Demo).
|
|
61
|
+
|
|
62
|
+
Please follow the documentation at [api-ref.agora.io](https://api-ref.agora.io/en/voice-sdk/web/4.x/index.html).
|
|
63
|
+
|
|
64
|
+
## Changelogs
|
|
65
|
+
|
|
66
|
+
Please check the changelogs at [Release Notes](https://docs.agora.io/en/video-calling/overview/release-notes?platform=web).
|
package/browser-shim.cjs
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browser-shim.cjs — HarmonyOS / Node.js browser-runtime shim for agora-rtc-sdk-ng.
|
|
3
|
+
*
|
|
4
|
+
* The Agora WebRTC SDK is authored for browsers: its UMD bundle dereferences
|
|
5
|
+
* `window` at module top level and its APIs expect browser globals
|
|
6
|
+
* (window, navigator, document, location, ...).
|
|
7
|
+
*
|
|
8
|
+
* Strategy (in order):
|
|
9
|
+
* 1. Browser/bundler environment (window already defined) → no-op.
|
|
10
|
+
* 2. jsdom available (declared as optionalDependency) → provision a real
|
|
11
|
+
* browser DOM environment. jsdom is a genuine DOM implementation — it is
|
|
12
|
+
* NOT a stub of the SDK's functionality. Preferred path.
|
|
13
|
+
* 3. jsdom unavailable (e.g. npm did not install optionalDependencies of a
|
|
14
|
+
* file: dependency) → minimal platform-adaptation layer that provides
|
|
15
|
+
* only the global object structure the UMD bundle needs to load. It does
|
|
16
|
+
* NOT fake any RTC capability.
|
|
17
|
+
*
|
|
18
|
+
* Scope / limitations (documented honestly):
|
|
19
|
+
* - Provided: window, document, navigator, location, DOM element classes.
|
|
20
|
+
* - NOT provided (deliberately, no verified implementation exists):
|
|
21
|
+
* * RTCPeerConnection / RTCDataChannel — no W3C-compatible WebRTC
|
|
22
|
+
* implementation is available for Node.js on HarmonyOS
|
|
23
|
+
* (@ohos-ports/node-datachannel exposes only libdatachannel's
|
|
24
|
+
* low-level PeerConnection API, not the W3C surface).
|
|
25
|
+
* * navigator.mediaDevices — camera/microphone capture is a browser
|
|
26
|
+
* capability.
|
|
27
|
+
* * W3C WebSocket signaling to Agora SD-RTN.
|
|
28
|
+
* RTC-dependent APIs report NOT_SUPPORTED at runtime.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
function noop() {}
|
|
32
|
+
|
|
33
|
+
function createEventTargetShim(target) {
|
|
34
|
+
const listeners = new Map();
|
|
35
|
+
target.addEventListener = function (type, fn) {
|
|
36
|
+
if (!listeners.has(type)) listeners.set(type, new Set());
|
|
37
|
+
listeners.get(type).add(fn);
|
|
38
|
+
};
|
|
39
|
+
target.removeEventListener = function (type, fn) {
|
|
40
|
+
if (listeners.has(type)) listeners.get(type).delete(fn);
|
|
41
|
+
};
|
|
42
|
+
target.dispatchEvent = function (event) {
|
|
43
|
+
const set = listeners.get(event && event.type);
|
|
44
|
+
if (set) for (const fn of Array.from(set)) fn(event);
|
|
45
|
+
return true;
|
|
46
|
+
};
|
|
47
|
+
return target;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createElementShim() {
|
|
51
|
+
return createEventTargetShim({
|
|
52
|
+
style: {},
|
|
53
|
+
children: [],
|
|
54
|
+
classList: { add: noop, remove: noop, toggle: noop },
|
|
55
|
+
setAttribute: noop,
|
|
56
|
+
getAttribute: () => null,
|
|
57
|
+
appendChild(child) { this.children.push(child); return child; },
|
|
58
|
+
removeChild: noop,
|
|
59
|
+
getContext: () => null,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Minimal platform-adaptation layer: enough global object structure for the
|
|
65
|
+
* UMD bundle to load under Node.js. Provides NO RTC capability.
|
|
66
|
+
*/
|
|
67
|
+
function applyMinimalShim() {
|
|
68
|
+
createEventTargetShim(globalThis);
|
|
69
|
+
if (typeof globalThis.window === "undefined") globalThis.window = globalThis;
|
|
70
|
+
if (typeof globalThis.self === "undefined") globalThis.self = globalThis;
|
|
71
|
+
|
|
72
|
+
if (typeof globalThis.location === "undefined") {
|
|
73
|
+
globalThis.location = {
|
|
74
|
+
href: "https://localhost:8080/",
|
|
75
|
+
origin: "https://localhost:8080",
|
|
76
|
+
protocol: "https:",
|
|
77
|
+
host: "localhost:8080",
|
|
78
|
+
hostname: "localhost",
|
|
79
|
+
port: "8080",
|
|
80
|
+
pathname: "/",
|
|
81
|
+
search: "",
|
|
82
|
+
hash: "",
|
|
83
|
+
toString() { return this.href; },
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (typeof globalThis.document === "undefined") {
|
|
88
|
+
globalThis.document = createEventTargetShim({
|
|
89
|
+
createElement: createElementShim,
|
|
90
|
+
createElementNS: createElementShim,
|
|
91
|
+
createTextNode: () => ({}),
|
|
92
|
+
documentElement: createEventTargetShim({ style: {} }),
|
|
93
|
+
head: createEventTargetShim({ appendChild: noop }),
|
|
94
|
+
body: createEventTargetShim({ appendChild: noop }),
|
|
95
|
+
getElementById: () => null,
|
|
96
|
+
querySelector: () => null,
|
|
97
|
+
querySelectorAll: () => [],
|
|
98
|
+
visibilityState: "visible",
|
|
99
|
+
hidden: false,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// DOM element/media type classes used for instanceof-style checks.
|
|
104
|
+
const classNames = [
|
|
105
|
+
"HTMLElement", "HTMLVideoElement", "HTMLAudioElement", "HTMLCanvasElement",
|
|
106
|
+
"HTMLDivElement", "HTMLImageElement", "HTMLIFrameElement", "HTMLScriptElement",
|
|
107
|
+
"MediaStream", "MediaStreamTrack", "MediaRecorder",
|
|
108
|
+
"Blob", "File", "FileReader", "FormData", "XMLHttpRequest",
|
|
109
|
+
"CustomEvent", "Event", "EventTarget",
|
|
110
|
+
"MessageChannel", "Worker",
|
|
111
|
+
"MutationObserver", "ResizeObserver", "IntersectionObserver", "PerformanceObserver",
|
|
112
|
+
"AudioContext", "AudioBuffer", "GainNode",
|
|
113
|
+
"MediaStreamAudioSourceNode", "MediaStreamAudioDestinationNode",
|
|
114
|
+
"ScreenOrientation", "ClipboardItem", "DOMException",
|
|
115
|
+
];
|
|
116
|
+
for (const name of classNames) {
|
|
117
|
+
if (typeof globalThis[name] === "undefined") {
|
|
118
|
+
globalThis[name] = class ShimClass {
|
|
119
|
+
constructor() { this.style = {}; }
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// AudioContext needs a minimal real-ish surface used during construction.
|
|
124
|
+
if (typeof globalThis.AudioContext === "function") {
|
|
125
|
+
const AC = globalThis.AudioContext;
|
|
126
|
+
if (!AC.prototype.createGain) {
|
|
127
|
+
AC.prototype.createGain = function () {
|
|
128
|
+
return {
|
|
129
|
+
gain: { value: 1, setValueAtTime: noop, linearRampToValueAtTime: noop },
|
|
130
|
+
connect: () => ({}), disconnect: noop,
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
AC.prototype.createMediaStreamSource = function () { return { connect: () => ({}) }; };
|
|
134
|
+
AC.prototype.createMediaStreamDestination = function () {
|
|
135
|
+
return { connect: noop, stream: { getTracks: () => [], getAudioTracks: () => [] } };
|
|
136
|
+
};
|
|
137
|
+
AC.prototype.decodeAudioData = function () { return Promise.reject(new Error("decodeAudioData: not supported in Node.js shim")); };
|
|
138
|
+
AC.prototype.resume = () => Promise.resolve();
|
|
139
|
+
AC.prototype.close = () => Promise.resolve();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function applyJSDomShim() {
|
|
146
|
+
const { JSDOM } = require("jsdom");
|
|
147
|
+
|
|
148
|
+
const dom = new JSDOM("<!doctype html><html><head></head><body></body></html>", {
|
|
149
|
+
url: "https://localhost:8080/",
|
|
150
|
+
pretendToBeVisual: true,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const g = globalThis;
|
|
154
|
+
const w = dom.window;
|
|
155
|
+
|
|
156
|
+
if (!g.window) g.window = w;
|
|
157
|
+
if (!g.document) g.document = w.document;
|
|
158
|
+
if (!g.navigator) g.navigator = w.navigator;
|
|
159
|
+
if (!g.location) g.location = w.location;
|
|
160
|
+
if (!g.self) g.self = w;
|
|
161
|
+
if (!g.HTMLElement) g.HTMLElement = w.HTMLElement;
|
|
162
|
+
if (!g.HTMLVideoElement) g.HTMLVideoElement = w.HTMLVideoElement;
|
|
163
|
+
if (!g.HTMLAudioElement) g.HTMLAudioElement = w.HTMLAudioElement;
|
|
164
|
+
if (!g.HTMLCanvasElement) g.HTMLCanvasElement = w.HTMLCanvasElement;
|
|
165
|
+
if (!g.MediaStream) g.MediaStream = w.MediaStream || class MediaStream { getTracks() { return []; } };
|
|
166
|
+
if (!g.MediaStreamTrack) g.MediaStreamTrack = w.MediaStreamTrack || class MediaStreamTrack {};
|
|
167
|
+
if (!g.Blob) g.Blob = w.Blob;
|
|
168
|
+
if (!g.FileReader) g.FileReader = w.FileReader;
|
|
169
|
+
if (!g.CustomEvent) g.CustomEvent = w.CustomEvent;
|
|
170
|
+
if (!g.Event) g.Event = w.Event;
|
|
171
|
+
if (!g.EventTarget) g.EventTarget = w.EventTarget;
|
|
172
|
+
if (!g.XMLHttpRequest) g.XMLHttpRequest = w.XMLHttpRequest;
|
|
173
|
+
if (!g.FormData) g.FormData = w.FormData;
|
|
174
|
+
if (!g.URL) g.URL = w.URL;
|
|
175
|
+
if (!g.URLSearchParams) g.URLSearchParams = w.URLSearchParams;
|
|
176
|
+
if (!g.requestAnimationFrame) g.requestAnimationFrame = w.requestAnimationFrame.bind(w);
|
|
177
|
+
if (!g.cancelAnimationFrame) g.cancelAnimationFrame = w.cancelAnimationFrame.bind(w);
|
|
178
|
+
|
|
179
|
+
// Round 6 fix: FormData realm mismatch breaking jsdom XHR multipart bodies.
|
|
180
|
+
//
|
|
181
|
+
// Node.js >= 18 ships a global FormData (from undici). The UMD bundle runs
|
|
182
|
+
// in the Node global scope, so `new FormData()` inside the SDK constructs an
|
|
183
|
+
// UNDICI FormData — the `if (!g.FormData) g.FormData = w.FormData` line above
|
|
184
|
+
// never fires because the global already exists. jsdom's XMLHttpRequest only
|
|
185
|
+
// recognises ITS OWN realm's FormData (via jsdom's internal FormData impl
|
|
186
|
+
// check); an undici FormData falls through the default branch of jsdom's
|
|
187
|
+
// send() and is coerced with ToString() to the literal string
|
|
188
|
+
// "[object FormData]" (17 bytes). Every multipart/form-data request the SDK
|
|
189
|
+
// makes (AP access point POST /api/v2/transpond/webrtc, webproxy join) then
|
|
190
|
+
// carries that garbage body: Agora's nginx returns 502 (no CORS headers on
|
|
191
|
+
// the error response), jsdom's CORS validator rejects it, and the SDK sees
|
|
192
|
+
// NETWORK_ERROR before the access-point layer is ever exercised.
|
|
193
|
+
//
|
|
194
|
+
// Fix: wrap jsdom XMLHttpRequest.prototype.send so a foreign (non-jsdom)
|
|
195
|
+
// FormData-like body is converted into a real jsdom FormData with the same
|
|
196
|
+
// entries. The resulting multipart/form-data body is genuinely serialized
|
|
197
|
+
// and sent — this is a realm-bridge polyfill, not a mock: the server
|
|
198
|
+
// receives the real field values the SDK appended.
|
|
199
|
+
try {
|
|
200
|
+
const jsdomXHR = w.XMLHttpRequest;
|
|
201
|
+
const jsdomFormData = w.FormData;
|
|
202
|
+
const origXhrSend = jsdomXHR.prototype.send;
|
|
203
|
+
jsdomXHR.prototype.send = function (body) {
|
|
204
|
+
let out = body;
|
|
205
|
+
if (
|
|
206
|
+
body &&
|
|
207
|
+
typeof body === "object" &&
|
|
208
|
+
typeof body.entries === "function" &&
|
|
209
|
+
typeof body.append === "function" &&
|
|
210
|
+
!(body instanceof jsdomFormData)
|
|
211
|
+
) {
|
|
212
|
+
const fd = new jsdomFormData();
|
|
213
|
+
for (const [name, value] of body.entries()) {
|
|
214
|
+
// Entries are strings (the SDK only appends string fields).
|
|
215
|
+
// Foreign Blob/File values are forwarded as-is; jsdom accepts any
|
|
216
|
+
// Blob for append.
|
|
217
|
+
fd.append(name, value);
|
|
218
|
+
}
|
|
219
|
+
out = fd;
|
|
220
|
+
}
|
|
221
|
+
return origXhrSend.call(this, out);
|
|
222
|
+
};
|
|
223
|
+
} catch (e) {
|
|
224
|
+
// If the jsdom prototype is not writable in this jsdom version, leave it
|
|
225
|
+
// untouched — behavior degrades to the pre-Round-6 state, nothing masked.
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// WebCrypto capability exposure (Round 4).
|
|
229
|
+
//
|
|
230
|
+
// Node.js >= 19 ships a genuine WebCrypto implementation as a global
|
|
231
|
+
// (globalThis.crypto.subtle). jsdom's window.crypto does NOT implement
|
|
232
|
+
// SubtleCrypto, so the SDK's capability probe
|
|
233
|
+
// supportWebCrypto = typeof window !== "undefined"
|
|
234
|
+
// && window.crypto !== undefined
|
|
235
|
+
// && window.crypto.subtle !== undefined
|
|
236
|
+
// reported false even though a real WebCrypto backend exists in the host
|
|
237
|
+
// runtime. Exposing the host's real implementation on the jsdom window
|
|
238
|
+
// makes the detection reflect actual capability (this is NOT a polyfill or
|
|
239
|
+
// stub — it is the runtime's own WebCrypto).
|
|
240
|
+
try {
|
|
241
|
+
const hostCrypto = globalThis.crypto;
|
|
242
|
+
if (
|
|
243
|
+
hostCrypto &&
|
|
244
|
+
typeof hostCrypto.subtle !== "undefined" &&
|
|
245
|
+
(typeof w.crypto === "undefined" || typeof w.crypto.subtle === "undefined")
|
|
246
|
+
) {
|
|
247
|
+
Object.defineProperty(w, "crypto", { value: hostCrypto, configurable: true });
|
|
248
|
+
}
|
|
249
|
+
} catch (e) {
|
|
250
|
+
// If the jsdom window rejects the property redefinition, keep jsdom's
|
|
251
|
+
// crypto object — detection stays honest (false), functionality unaffected.
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!g.getComputedStyle) g.getComputedStyle = w.getComputedStyle.bind(w);
|
|
255
|
+
if (!g.matchMedia) {
|
|
256
|
+
g.matchMedia = w.matchMedia
|
|
257
|
+
? w.matchMedia.bind(w)
|
|
258
|
+
: () => ({ matches: false, addListener: noop, removeListener: noop, addEventListener: noop, removeEventListener: noop });
|
|
259
|
+
}
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function createInMemoryStorage() {
|
|
264
|
+
const store = new Map();
|
|
265
|
+
return {
|
|
266
|
+
get length() { return store.size; },
|
|
267
|
+
key(i) { return Array.from(store.keys())[i] ?? null; },
|
|
268
|
+
getItem(k) { return store.has(String(k)) ? store.get(String(k)) : null; },
|
|
269
|
+
setItem(k, v) { store.set(String(k), String(v)); },
|
|
270
|
+
removeItem(k) { store.delete(String(k)); },
|
|
271
|
+
clear() { store.clear(); },
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Storage shims. In-memory Storage is a real (if non-persistent)
|
|
277
|
+
* implementation of the Web Storage API semantics.
|
|
278
|
+
*/
|
|
279
|
+
function applyStorageShim() {
|
|
280
|
+
if (typeof globalThis.localStorage === "undefined") {
|
|
281
|
+
Object.defineProperty(globalThis, "localStorage", {
|
|
282
|
+
value: createInMemoryStorage(), configurable: true, writable: false,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (typeof globalThis.sessionStorage === "undefined") {
|
|
286
|
+
Object.defineProperty(globalThis, "sessionStorage", {
|
|
287
|
+
value: createInMemoryStorage(), configurable: true, writable: false,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* navigator.mediaDevices shim — honest for a Node.js runtime where no media
|
|
294
|
+
* capture devices are accessible:
|
|
295
|
+
* - enumerateDevices() → resolves with an EMPTY list (factually correct:
|
|
296
|
+
* no devices are exposed to a Node.js process).
|
|
297
|
+
* - getUserMedia() → rejects (no capture devices available).
|
|
298
|
+
* This prevents the SDK's async device detection from producing unhandled
|
|
299
|
+
* rejections, without faking any device or capture capability.
|
|
300
|
+
*/
|
|
301
|
+
function applyMediaDevicesShim() {
|
|
302
|
+
const nav = globalThis.navigator;
|
|
303
|
+
if (!nav || nav.mediaDevices) return;
|
|
304
|
+
const notSupportedError = () => {
|
|
305
|
+
const err = new Error("getUserMedia is not supported in Node.js: no media capture devices available");
|
|
306
|
+
err.name = "NotSupportedError";
|
|
307
|
+
return err;
|
|
308
|
+
};
|
|
309
|
+
const mediaDevices = {
|
|
310
|
+
ondevicechange: null,
|
|
311
|
+
enumerateDevices: () => Promise.resolve([]),
|
|
312
|
+
getSupportedConstraints: () => ({}),
|
|
313
|
+
getUserMedia: () => Promise.reject(notSupportedError()),
|
|
314
|
+
getDisplayMedia: () => Promise.reject(notSupportedError()),
|
|
315
|
+
addEventListener: noop,
|
|
316
|
+
removeEventListener: noop,
|
|
317
|
+
dispatchEvent: () => true,
|
|
318
|
+
};
|
|
319
|
+
Object.defineProperty(nav, "mediaDevices", {
|
|
320
|
+
value: mediaDevices, configurable: true, writable: false,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* WebRTC runtime injection (Round 2).
|
|
326
|
+
*
|
|
327
|
+
* HarmonyOS Node.js does not ship a W3C WebRTC implementation. The
|
|
328
|
+
* @ohos-ports/node-datachannel package provides a native OHOS arm64
|
|
329
|
+
* libdatachannel stack (RTCPeerConnection / RTCDataChannel / ICE / DTLS /
|
|
330
|
+
* SCTP) exposed through a genuine W3C-shaped polyfill (verified with a real
|
|
331
|
+
* in-process loopback: SDP exchange, ICE, DTLS handshake, DataChannel open).
|
|
332
|
+
*
|
|
333
|
+
* When this package is installed, its real W3C-surface classes are promoted
|
|
334
|
+
* to globals so the SDK's RTC transport (client.join / publish signaling)
|
|
335
|
+
* can run:
|
|
336
|
+
* RTCPeerConnection, RTCSessionDescription, RTCIceCandidate,
|
|
337
|
+
* RTCDataChannel, RTCPeerConnectionIceEvent, RTCDataChannelEvent, RTCError.
|
|
338
|
+
*
|
|
339
|
+
* W3C WebSocket: Node.js >= 22 ships a real, spec-compliant WebSocket client
|
|
340
|
+
* as a global — used as-is (no shim, no override).
|
|
341
|
+
*
|
|
342
|
+
* Media capture (navigator.mediaDevices.getUserMedia) stays honest: see
|
|
343
|
+
* applyMediaDevicesShim — libdatachannel has no camera/microphone capture,
|
|
344
|
+
* so local track creation reports NotSupportedError. join() without locally
|
|
345
|
+
* pre-created tracks works over the native stack.
|
|
346
|
+
*/
|
|
347
|
+
function applyWebRtcRuntimeShim() {
|
|
348
|
+
// Real WebRTC already present (browser or a runtime that provides it).
|
|
349
|
+
if (typeof globalThis.RTCPeerConnection !== "undefined") return true;
|
|
350
|
+
|
|
351
|
+
let polyfill = null;
|
|
352
|
+
try {
|
|
353
|
+
polyfill = require("@ohos-ports/node-datachannel/polyfill");
|
|
354
|
+
} catch (e) {
|
|
355
|
+
// Native WebRTC runtime not installed — RTC globals stay absent (honest).
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
if (!polyfill || typeof polyfill.RTCPeerConnection !== "function") return false;
|
|
359
|
+
|
|
360
|
+
const rtcGlobals = [
|
|
361
|
+
"RTCPeerConnection",
|
|
362
|
+
"RTCSessionDescription",
|
|
363
|
+
"RTCIceCandidate",
|
|
364
|
+
"RTCDataChannel",
|
|
365
|
+
"RTCPeerConnectionIceEvent",
|
|
366
|
+
"RTCDataChannelEvent",
|
|
367
|
+
"RTCError",
|
|
368
|
+
];
|
|
369
|
+
for (const name of rtcGlobals) {
|
|
370
|
+
if (typeof globalThis[name] === "undefined" && typeof polyfill[name] !== "undefined") {
|
|
371
|
+
globalThis[name] = polyfill[name];
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Round 4: also promote the same real classes onto the browser `window`
|
|
376
|
+
// object. The SDK probes capabilities through window.* (e.g.
|
|
377
|
+
// window.RTCPeerConnection, window.RTCRtpSender) in several feature
|
|
378
|
+
// detectors; without this, those probes report false even though the real
|
|
379
|
+
// native WebRTC runtime is present and reachable via bare globals.
|
|
380
|
+
const jsdomWindow = globalThis.window;
|
|
381
|
+
if (jsdomWindow && jsdomWindow !== globalThis) {
|
|
382
|
+
for (const name of rtcGlobals) {
|
|
383
|
+
try {
|
|
384
|
+
if (
|
|
385
|
+
typeof jsdomWindow[name] === "undefined" &&
|
|
386
|
+
typeof polyfill[name] !== "undefined"
|
|
387
|
+
) {
|
|
388
|
+
Object.defineProperty(jsdomWindow, name, { value: polyfill[name], configurable: true, writable: true });
|
|
389
|
+
}
|
|
390
|
+
} catch (e) {
|
|
391
|
+
// Property not definable on this window implementation — leave absent
|
|
392
|
+
// so detection stays honest.
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function applyBrowserShim() {
|
|
400
|
+
// 1. Already a browser-like environment → nothing to do.
|
|
401
|
+
if (typeof globalThis.window !== "undefined" && globalThis.window.document) {
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// 2. Full DOM environment via jsdom (preferred).
|
|
406
|
+
let applied;
|
|
407
|
+
try {
|
|
408
|
+
applied = applyJSDomShim();
|
|
409
|
+
} catch (e) {
|
|
410
|
+
// jsdom not installed — fall through to minimal shim.
|
|
411
|
+
applied = applyMinimalShim();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// 3. Shared honest shims for storage and media device enumeration.
|
|
415
|
+
applyStorageShim();
|
|
416
|
+
applyMediaDevicesShim();
|
|
417
|
+
|
|
418
|
+
// 4. Native WebRTC runtime injection (Round 2).
|
|
419
|
+
applyWebRtcRuntimeShim();
|
|
420
|
+
|
|
421
|
+
return applied;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
module.exports = { applyBrowserShim };
|