im-rtc-call-engine 1.0.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/LICENSE +21 -0
- package/dist/bytes.d.ts +11 -0
- package/dist/bytes.d.ts.map +1 -0
- package/dist/bytes.js +20 -0
- package/dist/bytes.js.map +1 -0
- package/dist/callOptions.d.ts +27 -0
- package/dist/callOptions.d.ts.map +1 -0
- package/dist/callOptions.js +19 -0
- package/dist/callOptions.js.map +1 -0
- package/dist/deviceId.d.ts +10 -0
- package/dist/deviceId.d.ts.map +1 -0
- package/dist/deviceId.js +55 -0
- package/dist/deviceId.js.map +1 -0
- package/dist/engine.d.ts +302 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +489 -0
- package/dist/engine.js.map +1 -0
- package/dist/engineBus.d.ts +16 -0
- package/dist/engineBus.d.ts.map +1 -0
- package/dist/engineBus.js +89 -0
- package/dist/engineBus.js.map +1 -0
- package/dist/engineWiring.d.ts +40 -0
- package/dist/engineWiring.d.ts.map +1 -0
- package/dist/engineWiring.js +57 -0
- package/dist/engineWiring.js.map +1 -0
- package/dist/errors.d.ts +185 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +253 -0
- package/dist/errors.js.map +1 -0
- package/dist/eventBus.d.ts +24 -0
- package/dist/eventBus.d.ts.map +1 -0
- package/dist/eventBus.js +50 -0
- package/dist/eventBus.js.map +1 -0
- package/dist/events.d.ts +220 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +35 -0
- package/dist/events.js.map +1 -0
- package/dist/frameLoop.d.ts +93 -0
- package/dist/frameLoop.d.ts.map +1 -0
- package/dist/frameLoop.js +346 -0
- package/dist/frameLoop.js.map +1 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +64 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +101 -0
- package/dist/logger.js.map +1 -0
- package/dist/media/engineMediaApi.d.ts +40 -0
- package/dist/media/engineMediaApi.d.ts.map +1 -0
- package/dist/media/engineMediaApi.js +71 -0
- package/dist/media/engineMediaApi.js.map +1 -0
- package/dist/media/firstFrameGate.d.ts +47 -0
- package/dist/media/firstFrameGate.d.ts.map +1 -0
- package/dist/media/firstFrameGate.js +117 -0
- package/dist/media/firstFrameGate.js.map +1 -0
- package/dist/media/mediaAdapter.d.ts +135 -0
- package/dist/media/mediaAdapter.d.ts.map +1 -0
- package/dist/media/mediaAdapter.js +2 -0
- package/dist/media/mediaAdapter.js.map +1 -0
- package/dist/media/mediaBridge.d.ts +99 -0
- package/dist/media/mediaBridge.d.ts.map +1 -0
- package/dist/media/mediaBridge.js +186 -0
- package/dist/media/mediaBridge.js.map +1 -0
- package/dist/media/mediaPlane.d.ts +58 -0
- package/dist/media/mediaPlane.d.ts.map +1 -0
- package/dist/media/mediaPlane.js +119 -0
- package/dist/media/mediaPlane.js.map +1 -0
- package/dist/media/videoProfile.d.ts +79 -0
- package/dist/media/videoProfile.d.ts.map +1 -0
- package/dist/media/videoProfile.js +46 -0
- package/dist/media/videoProfile.js.map +1 -0
- package/dist/media/videoProfile.test.d.ts +2 -0
- package/dist/media/videoProfile.test.d.ts.map +1 -0
- package/dist/media/videoProfile.test.js +26 -0
- package/dist/media/videoProfile.test.js.map +1 -0
- package/dist/media/viewRegistry.d.ts +75 -0
- package/dist/media/viewRegistry.d.ts.map +1 -0
- package/dist/media/viewRegistry.js +130 -0
- package/dist/media/viewRegistry.js.map +1 -0
- package/dist/media/webrtcAdapter.d.ts +158 -0
- package/dist/media/webrtcAdapter.d.ts.map +1 -0
- package/dist/media/webrtcAdapter.js +502 -0
- package/dist/media/webrtcAdapter.js.map +1 -0
- package/dist/protocolId.d.ts +14 -0
- package/dist/protocolId.d.ts.map +1 -0
- package/dist/protocolId.js +109 -0
- package/dist/protocolId.js.map +1 -0
- package/dist/reasons.d.ts +63 -0
- package/dist/reasons.d.ts.map +1 -0
- package/dist/reasons.js +81 -0
- package/dist/reasons.js.map +1 -0
- package/dist/redact.d.ts +19 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +50 -0
- package/dist/redact.js.map +1 -0
- package/dist/signaling/backoff.d.ts +17 -0
- package/dist/signaling/backoff.d.ts.map +1 -0
- package/dist/signaling/backoff.js +23 -0
- package/dist/signaling/backoff.js.map +1 -0
- package/dist/signaling/candidate.d.ts +19 -0
- package/dist/signaling/candidate.d.ts.map +1 -0
- package/dist/signaling/candidate.js +22 -0
- package/dist/signaling/candidate.js.map +1 -0
- package/dist/signaling/caseMapping.d.ts +19 -0
- package/dist/signaling/caseMapping.d.ts.map +1 -0
- package/dist/signaling/caseMapping.js +34 -0
- package/dist/signaling/caseMapping.js.map +1 -0
- package/dist/signaling/connection.d.ts +110 -0
- package/dist/signaling/connection.d.ts.map +1 -0
- package/dist/signaling/connection.js +365 -0
- package/dist/signaling/connection.js.map +1 -0
- package/dist/signaling/connectionFactory.d.ts +55 -0
- package/dist/signaling/connectionFactory.d.ts.map +1 -0
- package/dist/signaling/connectionFactory.js +29 -0
- package/dist/signaling/connectionFactory.js.map +1 -0
- package/dist/signaling/connectionTypes.d.ts +94 -0
- package/dist/signaling/connectionTypes.d.ts.map +1 -0
- package/dist/signaling/connectionTypes.js +2 -0
- package/dist/signaling/connectionTypes.js.map +1 -0
- package/dist/signaling/discipline.d.ts +6 -0
- package/dist/signaling/discipline.d.ts.map +1 -0
- package/dist/signaling/discipline.js +100 -0
- package/dist/signaling/discipline.js.map +1 -0
- package/dist/signaling/enums.d.ts +53 -0
- package/dist/signaling/enums.d.ts.map +1 -0
- package/dist/signaling/enums.js +52 -0
- package/dist/signaling/enums.js.map +1 -0
- package/dist/signaling/envelope.d.ts +31 -0
- package/dist/signaling/envelope.d.ts.map +1 -0
- package/dist/signaling/envelope.js +91 -0
- package/dist/signaling/envelope.js.map +1 -0
- package/dist/signaling/fieldSpec.d.ts +101 -0
- package/dist/signaling/fieldSpec.d.ts.map +1 -0
- package/dist/signaling/fieldSpec.js +130 -0
- package/dist/signaling/fieldSpec.js.map +1 -0
- package/dist/signaling/frameSender.d.ts +34 -0
- package/dist/signaling/frameSender.d.ts.map +1 -0
- package/dist/signaling/frameSender.js +57 -0
- package/dist/signaling/frameSender.js.map +1 -0
- package/dist/signaling/frames.call.d.ts +299 -0
- package/dist/signaling/frames.call.d.ts.map +1 -0
- package/dist/signaling/frames.call.js +149 -0
- package/dist/signaling/frames.call.js.map +1 -0
- package/dist/signaling/frames.room.d.ts +566 -0
- package/dist/signaling/frames.room.d.ts.map +1 -0
- package/dist/signaling/frames.room.js +165 -0
- package/dist/signaling/frames.room.js.map +1 -0
- package/dist/signaling/frames.sys.d.ts +145 -0
- package/dist/signaling/frames.sys.d.ts.map +1 -0
- package/dist/signaling/frames.sys.js +53 -0
- package/dist/signaling/frames.sys.js.map +1 -0
- package/dist/signaling/handshakeGiveUp.d.ts +36 -0
- package/dist/signaling/handshakeGiveUp.d.ts.map +1 -0
- package/dist/signaling/handshakeGiveUp.js +48 -0
- package/dist/signaling/handshakeGiveUp.js.map +1 -0
- package/dist/signaling/heartbeat.d.ts +34 -0
- package/dist/signaling/heartbeat.d.ts.map +1 -0
- package/dist/signaling/heartbeat.js +52 -0
- package/dist/signaling/heartbeat.js.map +1 -0
- package/dist/signaling/pendingRequests.d.ts +33 -0
- package/dist/signaling/pendingRequests.d.ts.map +1 -0
- package/dist/signaling/pendingRequests.js +57 -0
- package/dist/signaling/pendingRequests.js.map +1 -0
- package/dist/signaling/reconnector.d.ts +43 -0
- package/dist/signaling/reconnector.d.ts.map +1 -0
- package/dist/signaling/reconnector.js +76 -0
- package/dist/signaling/reconnector.js.map +1 -0
- package/dist/signaling/registry.d.ts +100 -0
- package/dist/signaling/registry.d.ts.map +1 -0
- package/dist/signaling/registry.js +217 -0
- package/dist/signaling/registry.js.map +1 -0
- package/dist/signaling/resumeDeadline.d.ts +48 -0
- package/dist/signaling/resumeDeadline.d.ts.map +1 -0
- package/dist/signaling/resumeDeadline.js +89 -0
- package/dist/signaling/resumeDeadline.js.map +1 -0
- package/dist/signaling/tokenExpiry.d.ts +89 -0
- package/dist/signaling/tokenExpiry.d.ts.map +1 -0
- package/dist/signaling/tokenExpiry.js +106 -0
- package/dist/signaling/tokenExpiry.js.map +1 -0
- package/dist/signaling/webSocket.d.ts +59 -0
- package/dist/signaling/webSocket.d.ts.map +1 -0
- package/dist/signaling/webSocket.js +50 -0
- package/dist/signaling/webSocket.js.map +1 -0
- package/dist/state/callMachine.d.ts +56 -0
- package/dist/state/callMachine.d.ts.map +1 -0
- package/dist/state/callMachine.js +176 -0
- package/dist/state/callMachine.js.map +1 -0
- package/dist/state/callRecv.d.ts +12 -0
- package/dist/state/callRecv.d.ts.map +1 -0
- package/dist/state/callRecv.js +255 -0
- package/dist/state/callRecv.js.map +1 -0
- package/dist/state/engineMachine.d.ts +41 -0
- package/dist/state/engineMachine.d.ts.map +1 -0
- package/dist/state/engineMachine.js +210 -0
- package/dist/state/engineMachine.js.map +1 -0
- package/dist/state/forceEnd.d.ts +44 -0
- package/dist/state/forceEnd.d.ts.map +1 -0
- package/dist/state/forceEnd.js +96 -0
- package/dist/state/forceEnd.js.map +1 -0
- package/dist/state/roomMachine.d.ts +92 -0
- package/dist/state/roomMachine.d.ts.map +1 -0
- package/dist/state/roomMachine.js +319 -0
- package/dist/state/roomMachine.js.map +1 -0
- package/dist/state/roomRecv.d.ts +7 -0
- package/dist/state/roomRecv.d.ts.map +1 -0
- package/dist/state/roomRecv.js +209 -0
- package/dist/state/roomRecv.js.map +1 -0
- package/dist/state/types.d.ts +61 -0
- package/dist/state/types.d.ts.map +1 -0
- package/dist/state/types.js +30 -0
- package/dist/state/types.js.map +1 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +11 -0
- package/dist/version.js.map +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import { ErrorCode, RtcError } from '../errors.js';
|
|
2
|
+
import { logger, redactCandidate, redactSdp } from '../logger.js';
|
|
3
|
+
import { defaultVideoProfile, simulcastEncodings, videoConstraints } from './videoProfile.js';
|
|
4
|
+
/**
|
|
5
|
+
* 浏览器 WebRTC 的媒体适配器。
|
|
6
|
+
*
|
|
7
|
+
* **两条 PeerConnection,各有固定 offerer**(协议 §3.3):pub 由本端 offer、
|
|
8
|
+
* sub 由服务端 offer。固定 offerer 就没有 glare,所以这里**没有一行**
|
|
9
|
+
* perfect negotiation / rollback。
|
|
10
|
+
*
|
|
11
|
+
* **不配 iceServers**:SFU 有公网 IP,客户端直连它,不部署 TURN(拍板 §11-2)。
|
|
12
|
+
*/
|
|
13
|
+
export class WebRTCAdapter {
|
|
14
|
+
source;
|
|
15
|
+
pub = null;
|
|
16
|
+
sub = null;
|
|
17
|
+
events = null;
|
|
18
|
+
/** cid → 本端轨道。cid 就是轨道的 id(见 LocalTrackInfo)。 */
|
|
19
|
+
locals = new Map();
|
|
20
|
+
/** 候选缓存:PC 的远端描述还没设时收到的候选先存着(乱序是常态)。 */
|
|
21
|
+
pendingCandidates = { pub: [], sub: [] };
|
|
22
|
+
/** 采集画质档位。见 videoProfile.ts:**策略归宿主**,不是服务端下发的。 */
|
|
23
|
+
video;
|
|
24
|
+
/** 已经在预览的摄像头。发布时复用它,不重开设备。 */
|
|
25
|
+
preview = null;
|
|
26
|
+
/** 预览那条轨道是否已经挂到 pub 上。`addTrack` 同一条轨道两次会抛异常。 */
|
|
27
|
+
cameraPublished = false;
|
|
28
|
+
/**
|
|
29
|
+
* acquireCamera 已经认领了预览(发布中或已发布)。**进门就置位**,不等预览起完:
|
|
30
|
+
* 预览还在路上时来一句 `stopLocalPreview`,不能把马上要发布的这条停掉。
|
|
31
|
+
*/
|
|
32
|
+
cameraClaimed = false;
|
|
33
|
+
/** 摄像头挂上 pub 的 sender。通话中关了再开要靠它 `replaceTrack`,不重新协商。 */
|
|
34
|
+
cameraSender = null;
|
|
35
|
+
/**
|
|
36
|
+
* 预览意图的序号:每次 start / stop 加一。
|
|
37
|
+
* `stopLocalPreview` 等完在起的那一次之后序号变了,说明这期间又有人要了预览——听后来的。
|
|
38
|
+
*/
|
|
39
|
+
previewIntent = 0;
|
|
40
|
+
/** 通话中开关摄像头排成一队:连点时上一次的 getUserMedia 还没回来,下一次不能插队。 */
|
|
41
|
+
cameraToggle = Promise.resolve();
|
|
42
|
+
/** 已经拿到并挂在 pub 上的麦克风轨道 cid;没有是 null。见 {@link publishedMicrophoneCid}。 */
|
|
43
|
+
micCid = null;
|
|
44
|
+
/** 麦克风权限已经探过一次。见 `probeMicrophone`:探测不是免费的。 */
|
|
45
|
+
micProbed = false;
|
|
46
|
+
/** 摄像头权限已经探过一次。见 `probeCamera`。 */
|
|
47
|
+
cameraProbed = false;
|
|
48
|
+
/**
|
|
49
|
+
* 正在起的那次预览。**并发的 startLocalPreview / acquireCamera / probeCamera 共用这一次 getUserMedia**。
|
|
50
|
+
*
|
|
51
|
+
* 来电页的预览还没起完、用户就点了接听:接听路径要探权限、要起预览、进房后还要发布,
|
|
52
|
+
* 三处各自去 `getUserMedia` 就是把摄像头开两三次——多出来的那条流没人收,指示灯灭不掉。
|
|
53
|
+
*/
|
|
54
|
+
previewOpening = null;
|
|
55
|
+
/** close() 一次加一。起到一半被 close 掉的预览靠它认出自己已经作废。 */
|
|
56
|
+
closeGeneration = 0;
|
|
57
|
+
/** source 缺省时用 navigator.mediaDevices;端到端测试可以传合成源。 */
|
|
58
|
+
constructor(source, videoProfile) {
|
|
59
|
+
this.source = source ?? {
|
|
60
|
+
getStream: (constraints) => navigator.mediaDevices.getUserMedia(constraints),
|
|
61
|
+
};
|
|
62
|
+
this.video = videoProfile ?? defaultVideoProfile;
|
|
63
|
+
}
|
|
64
|
+
open(events) {
|
|
65
|
+
this.events = events;
|
|
66
|
+
this.pub = this.createPeer('pub');
|
|
67
|
+
this.sub = this.createPeer('sub');
|
|
68
|
+
this.sub.ontrack = (event) => {
|
|
69
|
+
const track = event.track;
|
|
70
|
+
// 下行轨道的 id 就是协议里的 track_id(服务端把 msid 设成了它)。
|
|
71
|
+
logger.info('收到下行轨道', { track_id: track.id, kind: track.kind });
|
|
72
|
+
events.onRemoteTrack(track.id, track);
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** 下一个上行 offer 要不要带 ICE restart。见 restartPubICE()。 */
|
|
76
|
+
pubICERestartPending = false;
|
|
77
|
+
createPeer(role) {
|
|
78
|
+
const peer = new RTCPeerConnection({ iceServers: [] });
|
|
79
|
+
peer.onicecandidate = (event) => {
|
|
80
|
+
if (event.candidate === null)
|
|
81
|
+
return; // 收集结束;服务端不需要这条也能工作
|
|
82
|
+
const init = event.candidate.toJSON();
|
|
83
|
+
logger.debug('本端候选', { pc: role, candidate: redactCandidate(init.candidate ?? '') });
|
|
84
|
+
this.events?.onLocalCandidate(role, init);
|
|
85
|
+
};
|
|
86
|
+
peer.onconnectionstatechange = () => {
|
|
87
|
+
logger.info('PC 状态变化', { pc: role, state: peer.connectionState });
|
|
88
|
+
this.events?.onConnectionStateChange(role, peer.connectionState);
|
|
89
|
+
};
|
|
90
|
+
return peer;
|
|
91
|
+
}
|
|
92
|
+
async acquireMicrophone() {
|
|
93
|
+
return this.acquire({ audio: true }, 'audio', 'microphone');
|
|
94
|
+
}
|
|
95
|
+
async probeMicrophone() {
|
|
96
|
+
/*
|
|
97
|
+
**探过一次就不再探。** 一次探测要向 `MediaSource` 要一份完整的流,而源在那条流
|
|
98
|
+
背后建了什么、又由谁来收,适配器是不知道的——Demo 的合成源每次 `getStream` 都新建
|
|
99
|
+
一个 `AudioContext` + 振荡器,只在 `source.stop()` 里收。停掉轨道收不掉它们,
|
|
100
|
+
于是**每拨一次号泄漏一个 AudioContext**;浏览器对同一页面的 AudioContext 有个位数的
|
|
101
|
+
上限,几通电话之后 `new AudioContext()` 直接抛,麦克风就再也发布不出去了。
|
|
102
|
+
|
|
103
|
+
权限一旦给过就不会自己收回(真收回了,随后的 `acquireMicrophone` 会照常抛 2001,
|
|
104
|
+
界面还是能正确降级),所以缓存这一个布尔值是安全的。
|
|
105
|
+
*/
|
|
106
|
+
if (this.micProbed)
|
|
107
|
+
return;
|
|
108
|
+
const stream = await this.getStreamOrThrow({ audio: true });
|
|
109
|
+
// 探完就放:这条轨道只是为了让权限框弹出来,留着会让麦克风指示灯一直亮。
|
|
110
|
+
for (const track of stream.getTracks())
|
|
111
|
+
track.stop();
|
|
112
|
+
this.micProbed = true;
|
|
113
|
+
}
|
|
114
|
+
async probeCamera() {
|
|
115
|
+
// 与 probeMicrophone 同一个缓存理由;已经在预览说明权限早就拿到了。
|
|
116
|
+
if (this.cameraProbed || this.preview !== null)
|
|
117
|
+
return;
|
|
118
|
+
// 预览正在起:它的结果就是探测结果(失败时抛的是同一个错误),再开一次会抢同一个摄像头。
|
|
119
|
+
if (this.previewOpening !== null) {
|
|
120
|
+
await this.previewOpening;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const stream = await this.getStreamOrThrow({ video: videoConstraints(this.video) });
|
|
124
|
+
// 探完就放:只为让权限框弹出来,留着摄像头指示灯会一直亮——而用户可能根本没开摄像头。
|
|
125
|
+
for (const track of stream.getTracks())
|
|
126
|
+
track.stop();
|
|
127
|
+
this.cameraProbed = true;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* startLocalPreview 只起采集,不挂到 pub 上。
|
|
131
|
+
*
|
|
132
|
+
* **拨出中就该看得见自己**(草图 §03-E),可那时还没有房间、推不了流。
|
|
133
|
+
* 所以采集与发布拆成两步,`acquireCamera` 复用这条轨道——
|
|
134
|
+
* 不复用的话第二次 `getUserMedia` 会去抢同一个摄像头。
|
|
135
|
+
*/
|
|
136
|
+
async startLocalPreview() {
|
|
137
|
+
this.previewIntent += 1;
|
|
138
|
+
if (this.preview !== null)
|
|
139
|
+
return previewInfo(this.preview.cid);
|
|
140
|
+
// 单飞:已经有一次在起就等它,见 `previewOpening`。
|
|
141
|
+
if (this.previewOpening === null)
|
|
142
|
+
this.previewOpening = this.openPreview(this.closeGeneration);
|
|
143
|
+
return this.previewOpening;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* stopLocalPreview 停掉**还没发布**的预览,摄像头指示灯随之熄灭(交互稿 §01 v3.7)。
|
|
147
|
+
*
|
|
148
|
+
* 进房前(来电页 / 拨出中)关摄像头原先只是把画面藏起来,采集一直开到通话结束。
|
|
149
|
+
* 已经发布(或正在发布)的不停:通话中关摄像头走 `setMuted`,发送器与协商要留着。
|
|
150
|
+
*/
|
|
151
|
+
async stopLocalPreview() {
|
|
152
|
+
this.previewIntent += 1;
|
|
153
|
+
const intent = this.previewIntent;
|
|
154
|
+
// 预览还在起:等它落地再停。直接放过的话,它回来时摄像头就亮着没人管了。
|
|
155
|
+
if (this.previewOpening !== null)
|
|
156
|
+
await this.previewOpening.catch(() => undefined);
|
|
157
|
+
if (intent !== this.previewIntent || this.cameraClaimed)
|
|
158
|
+
return;
|
|
159
|
+
const current = this.preview;
|
|
160
|
+
if (current === null)
|
|
161
|
+
return;
|
|
162
|
+
this.preview = null;
|
|
163
|
+
this.locals.delete(current.cid);
|
|
164
|
+
current.track.stop();
|
|
165
|
+
// 能起过预览说明权限早就拿到了,之后的 probeCamera 不必再开一次设备。
|
|
166
|
+
this.cameraProbed = true;
|
|
167
|
+
logger.info('进房前关摄像头,预览采集已停', { cid: current.cid });
|
|
168
|
+
}
|
|
169
|
+
async openPreview(generation) {
|
|
170
|
+
try {
|
|
171
|
+
const stream = await this.getStreamOrThrow({ video: videoConstraints(this.video) });
|
|
172
|
+
if (generation !== this.closeGeneration) {
|
|
173
|
+
// 起到一半通话结束了(close 已经跑过):这条流再没人会收,当场放掉,指示灯才会灭。
|
|
174
|
+
for (const track of stream.getTracks())
|
|
175
|
+
track.stop();
|
|
176
|
+
throw new RtcError(ErrorCode.invalidState, { cause: new Error('预览起到一半媒体层已关闭') });
|
|
177
|
+
}
|
|
178
|
+
const track = stream.getVideoTracks()[0];
|
|
179
|
+
if (track === undefined) {
|
|
180
|
+
throw new RtcError(ErrorCode.deviceNotFound, {
|
|
181
|
+
cause: new Error('getUserMedia 没返回 video 轨道'),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
this.preview = { cid: track.id, track, stream, paused: false };
|
|
185
|
+
this.locals.set(track.id, track);
|
|
186
|
+
return previewInfo(track.id);
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
// 只收自己这一代的:close 之后新起的那次预览不能被上一代的收尾抹掉。
|
|
190
|
+
if (generation === this.closeGeneration)
|
|
191
|
+
this.previewOpening = null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async acquireCamera(simulcast = true) {
|
|
195
|
+
// **复用预览那条轨道**:拨出时已经开过摄像头了,再开一次会抢设备。
|
|
196
|
+
this.cameraClaimed = true;
|
|
197
|
+
let info;
|
|
198
|
+
try {
|
|
199
|
+
info = await this.startLocalPreview();
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
this.cameraClaimed = this.cameraPublished;
|
|
203
|
+
throw err;
|
|
204
|
+
}
|
|
205
|
+
if (this.preview === null || this.cameraPublished)
|
|
206
|
+
return info;
|
|
207
|
+
this.cameraPublished = true;
|
|
208
|
+
this.cameraSender = this.addVideoTrack(this.preview.track, this.preview.stream, simulcast);
|
|
209
|
+
return info;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* addVideoTrack 把一条上行视频挂到 pub 上。
|
|
213
|
+
*
|
|
214
|
+
* # 为什么不能用 addTrack
|
|
215
|
+
*
|
|
216
|
+
* `addTrack` 只会产生**一个 encoding**,浏览器里发 simulcast 必须在建
|
|
217
|
+
* transceiver 时就把 `sendEncodings` 给出来——协商之后再 `setParameters`
|
|
218
|
+
* 加层是加不上的(规范不允许改 encoding 的条数)。
|
|
219
|
+
*
|
|
220
|
+
* 这正是之前那个洞:`publishCamera(simulcast = true)` 的这个参数一路传进了
|
|
221
|
+
* `room.publish` 帧、**告诉服务端「我是 simulcast」**,可媒体面走的是裸
|
|
222
|
+
* `addTrack`,实际只发一层。服务端于是只看到空 RID 的单层(`ridToLayer("")`
|
|
223
|
+
* 当成 h),层选择无从谈起:订阅者报 `l` 也只能收到全速率的 h
|
|
224
|
+
* (`selectLayer` 的兜底),弱下行的那一方被自己的全速率流压死。
|
|
225
|
+
*
|
|
226
|
+
* `streams: [stream]` 不能省:msid 的第二段就是 cid,服务端靠它认领 m-line
|
|
227
|
+
* (协议 §3.2)。省掉它服务端永远认不回这条轨道。
|
|
228
|
+
*/
|
|
229
|
+
addVideoTrack(track, stream, simulcast) {
|
|
230
|
+
const pub = this.requirePub();
|
|
231
|
+
if (!simulcast) {
|
|
232
|
+
const sender = pub.addTrack(track, stream);
|
|
233
|
+
this.applyVideoBitrate(sender);
|
|
234
|
+
return sender;
|
|
235
|
+
}
|
|
236
|
+
const transceiver = pub.addTransceiver(track, {
|
|
237
|
+
direction: 'sendonly',
|
|
238
|
+
streams: [stream],
|
|
239
|
+
sendEncodings: simulcastEncodings(this.video),
|
|
240
|
+
});
|
|
241
|
+
logger.info('上行视频已按 simulcast 发布', {
|
|
242
|
+
cid: track.id,
|
|
243
|
+
layers: simulcastEncodings(this.video).map((e) => e.rid).join(','),
|
|
244
|
+
});
|
|
245
|
+
this.applyVideoBitrate(transceiver.sender);
|
|
246
|
+
return transceiver.sender;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* acquire 拿一条本端轨道并挂到 pub PC 上。
|
|
250
|
+
*
|
|
251
|
+
* `getUserMedia` **只在 localhost / HTTPS 可用**;权限被拒要有明确的界面态与重试,
|
|
252
|
+
* **不弹 alert**(CONVENTIONS §8)——所以这里只抛结构化错误,界面怎么表现由 uikit 决定。
|
|
253
|
+
*/
|
|
254
|
+
async acquire(constraints, kind, source) {
|
|
255
|
+
const pub = this.requirePub();
|
|
256
|
+
const stream = await this.getStreamOrThrow(constraints);
|
|
257
|
+
const track = kind === 'audio' ? stream.getAudioTracks()[0] : stream.getVideoTracks()[0];
|
|
258
|
+
if (track === undefined) {
|
|
259
|
+
throw new RtcError(ErrorCode.deviceNotFound, {
|
|
260
|
+
cause: new Error(`getUserMedia 没返回 ${kind} 轨道`),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
if (kind === 'video') {
|
|
264
|
+
this.addVideoTrack(track, stream, true);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
pub.addTrack(track, stream);
|
|
268
|
+
this.micCid = track.id;
|
|
269
|
+
}
|
|
270
|
+
this.locals.set(track.id, track);
|
|
271
|
+
return { cid: track.id, kind, source };
|
|
272
|
+
}
|
|
273
|
+
publishedMicrophoneCid() {
|
|
274
|
+
return this.micCid;
|
|
275
|
+
}
|
|
276
|
+
/** 摄像头「已发布」= 预览那条轨道真的挂上了 pub(`cameraPublished`),只在预览不算。 */
|
|
277
|
+
publishedCameraCid() {
|
|
278
|
+
return this.cameraPublished && this.preview !== null ? this.preview.cid : null;
|
|
279
|
+
}
|
|
280
|
+
/** getStreamOrThrow 取流,并把浏览器的异常收敛成结构化错误。 */
|
|
281
|
+
async getStreamOrThrow(constraints) {
|
|
282
|
+
try {
|
|
283
|
+
return await this.source.getStream(constraints);
|
|
284
|
+
}
|
|
285
|
+
catch (cause) {
|
|
286
|
+
const code = isPermissionError(cause)
|
|
287
|
+
? ErrorCode.devicePermissionDenied
|
|
288
|
+
: ErrorCode.deviceNotFound;
|
|
289
|
+
throw new RtcError(code, { cause });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* applyVideoBitrate 给上行视频压一个码率上限。
|
|
294
|
+
*
|
|
295
|
+
* **不设的话浏览器会自己往上飙**:Chrome 对 720p 的默认上限远高于我们给
|
|
296
|
+
* simulcast h 层定的目标值,服务端的带宽预算(`bwe.go` 的 `bitrateHigh`)
|
|
297
|
+
* 就成了一个对不上的数字,降层判断跟着不准。
|
|
298
|
+
*
|
|
299
|
+
* 失败只记日志:码率是画质偏好,`setParameters` 被拒不该让通话打不出去。
|
|
300
|
+
*/
|
|
301
|
+
applyVideoBitrate(sender) {
|
|
302
|
+
const params = sender.getParameters();
|
|
303
|
+
// encodings 可能还是空的(协商之前);补一个默认项,浏览器会认。
|
|
304
|
+
if (params.encodings.length === 0)
|
|
305
|
+
params.encodings = [{}];
|
|
306
|
+
/*
|
|
307
|
+
**simulcast 的三层各有各的码率,不能抹平成同一个值。**
|
|
308
|
+
全设成 h 的目标码率等于让 l / m 两层也按 1.5Mbps 发,
|
|
309
|
+
上行瞬间涨到三倍,而降层根本省不下带宽——降了个寂寞。
|
|
310
|
+
按 rid 对号入座;没有 rid(单层发布)才用整档的上限。
|
|
311
|
+
*/
|
|
312
|
+
const byRid = new Map(simulcastEncodings(this.video).map((e) => [e.rid, e.maxBitrate]));
|
|
313
|
+
for (const encoding of params.encodings) {
|
|
314
|
+
encoding.maxBitrate = byRid.get(encoding.rid) ?? this.video.maxBitrateBps;
|
|
315
|
+
}
|
|
316
|
+
void sender.setParameters(params).catch((err) => {
|
|
317
|
+
logger.info('设置上行码率失败,用浏览器默认值', { err: String(err) });
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
async createPubOffer() {
|
|
321
|
+
const pub = this.requirePub();
|
|
322
|
+
const iceRestart = this.pubICERestartPending;
|
|
323
|
+
this.pubICERestartPending = false;
|
|
324
|
+
if (iceRestart)
|
|
325
|
+
logger.info('上行重启 ICE', {});
|
|
326
|
+
const offer = await pub.createOffer({ iceRestart });
|
|
327
|
+
await pub.setLocalDescription(offer);
|
|
328
|
+
logger.debug('生成上行 offer', { sdp: redactSdp(offer.sdp) });
|
|
329
|
+
return offer.sdp ?? '';
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* restartPubICE 让**下一个**上行 offer 带上 ICE restart(换一对新 ufrag/pwd 重新打洞)。
|
|
333
|
+
*
|
|
334
|
+
* `pub` 那条 PC 的 offerer 恒为本端(协议 §3.3),它 `failed` 了只能自己救;
|
|
335
|
+
* `sub` 那条由服务端救——**各自重启自己 offer 的那条**,不需要新的协议帧,也不会 glare。
|
|
336
|
+
* 做成「置一个位、下一个 offer 生效」而不是「立刻发帧」:发帧是 engine 的事,媒体层不认识信令。
|
|
337
|
+
*/
|
|
338
|
+
restartPubICE() {
|
|
339
|
+
this.pubICERestartPending = true;
|
|
340
|
+
}
|
|
341
|
+
async applyPubAnswer(sdp) {
|
|
342
|
+
const pub = this.requirePub();
|
|
343
|
+
await pub.setRemoteDescription({ type: 'answer', sdp });
|
|
344
|
+
await this.drainCandidates('pub');
|
|
345
|
+
}
|
|
346
|
+
async answerSubOffer(sdp) {
|
|
347
|
+
const sub = this.requireSub();
|
|
348
|
+
await sub.setRemoteDescription({ type: 'offer', sdp });
|
|
349
|
+
await this.drainCandidates('sub');
|
|
350
|
+
const answer = await sub.createAnswer();
|
|
351
|
+
await sub.setLocalDescription(answer);
|
|
352
|
+
return answer.sdp ?? '';
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* addRemoteCandidate 加一个远端候选。
|
|
356
|
+
*
|
|
357
|
+
* 远端描述还没设时**缓存起来而不是报错**——trickle ICE 与 offer/answer 天然会赛跑,
|
|
358
|
+
* 协议要求容忍(§3.3)。
|
|
359
|
+
*/
|
|
360
|
+
async addRemoteCandidate(pc, candidate) {
|
|
361
|
+
if (candidate.candidate === undefined || candidate.candidate === '')
|
|
362
|
+
return;
|
|
363
|
+
const peer = pc === 'pub' ? this.pub : this.sub;
|
|
364
|
+
if (peer === null)
|
|
365
|
+
return;
|
|
366
|
+
if (peer.remoteDescription === null) {
|
|
367
|
+
this.pendingCandidates[pc].push(candidate);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
await peer.addIceCandidate(candidate);
|
|
372
|
+
}
|
|
373
|
+
catch (cause) {
|
|
374
|
+
logger.debug('候选加不进去,忽略', {
|
|
375
|
+
pc,
|
|
376
|
+
candidate: redactCandidate(candidate.candidate),
|
|
377
|
+
cause: String(cause),
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
async drainCandidates(pc) {
|
|
382
|
+
const peer = pc === 'pub' ? this.pub : this.sub;
|
|
383
|
+
if (peer === null)
|
|
384
|
+
return;
|
|
385
|
+
const queued = this.pendingCandidates[pc].splice(0);
|
|
386
|
+
for (const candidate of queued) {
|
|
387
|
+
try {
|
|
388
|
+
await peer.addIceCandidate(candidate);
|
|
389
|
+
}
|
|
390
|
+
catch (cause) {
|
|
391
|
+
logger.debug('缓存的候选加不进去,忽略', { pc, cause: String(cause) });
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
async setMuted(cid, muted) {
|
|
396
|
+
const track = this.locals.get(cid);
|
|
397
|
+
if (track === undefined)
|
|
398
|
+
return;
|
|
399
|
+
// enabled=false 会让浏览器发静音帧/黑帧而不是断流——协商与 Track 都保留,
|
|
400
|
+
// 这正是 mute 与 unpublish 的区别。
|
|
401
|
+
track.enabled = !muted;
|
|
402
|
+
const camera = this.preview;
|
|
403
|
+
if (camera === null || camera.cid !== cid || !this.cameraPublished)
|
|
404
|
+
return;
|
|
405
|
+
// 代数在**调用时**记下:排队期间 close() 过的话,轮到它时要认得出自己已经过期。
|
|
406
|
+
const generation = this.closeGeneration;
|
|
407
|
+
const run = this.cameraToggle.then(() => this.syncCameraCapture(camera, muted, generation));
|
|
408
|
+
this.cameraToggle = run.catch(() => undefined);
|
|
409
|
+
await run;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* syncCameraCapture 让已发布摄像头的采集跟上开关(交互稿 §01 v3.7:通话中关摄像头也停采集)。
|
|
413
|
+
*
|
|
414
|
+
* 光 `enabled=false` 的话画面是黑了,**摄像头指示灯却一直亮着**。所以关 = `stop()` 掉轨道;
|
|
415
|
+
* 开 = 重新 getUserMedia,再用 `replaceTrack` 换到同一个 sender 上——
|
|
416
|
+
* transceiver、msid、cid 都不变,**不重新协商**,服务端只看到这条 Track 又有包了。
|
|
417
|
+
* 重新采集可能被拒(用户刚在浏览器里收回了权限),错误原样抛给调用方,轨道留在停着的状态。
|
|
418
|
+
*/
|
|
419
|
+
async syncCameraCapture(camera, muted, generation) {
|
|
420
|
+
const stale = () => generation !== this.closeGeneration || this.preview !== camera;
|
|
421
|
+
if (stale())
|
|
422
|
+
return;
|
|
423
|
+
if (muted) {
|
|
424
|
+
if (camera.paused)
|
|
425
|
+
return;
|
|
426
|
+
camera.paused = true;
|
|
427
|
+
camera.track.stop();
|
|
428
|
+
logger.info('通话中关摄像头,采集已停(发送器与协商保留)', { cid: camera.cid });
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (!camera.paused)
|
|
432
|
+
return;
|
|
433
|
+
const stream = await this.getStreamOrThrow({ video: videoConstraints(this.video) });
|
|
434
|
+
const track = stream.getVideoTracks()[0];
|
|
435
|
+
if (stale() || track === undefined) {
|
|
436
|
+
for (const t of stream.getTracks())
|
|
437
|
+
t.stop();
|
|
438
|
+
if (stale())
|
|
439
|
+
return;
|
|
440
|
+
throw new RtcError(ErrorCode.deviceNotFound, { cause: new Error('getUserMedia 没返回 video 轨道') });
|
|
441
|
+
}
|
|
442
|
+
try {
|
|
443
|
+
await this.cameraSender?.replaceTrack(track);
|
|
444
|
+
}
|
|
445
|
+
catch (cause) {
|
|
446
|
+
track.stop();
|
|
447
|
+
throw new RtcError(ErrorCode.invalidState, { cause });
|
|
448
|
+
}
|
|
449
|
+
if (stale()) {
|
|
450
|
+
track.stop();
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
camera.track = track;
|
|
454
|
+
camera.paused = false;
|
|
455
|
+
this.locals.set(camera.cid, track);
|
|
456
|
+
logger.info('通话中重新打开摄像头,已换上新采集的轨道', { cid: camera.cid });
|
|
457
|
+
}
|
|
458
|
+
localTrack(cid) {
|
|
459
|
+
return this.locals.get(cid);
|
|
460
|
+
}
|
|
461
|
+
close() {
|
|
462
|
+
// 轨道用完必须 stop(),否则摄像头指示灯不灭(CONVENTIONS §8)。
|
|
463
|
+
for (const track of this.locals.values())
|
|
464
|
+
track.stop();
|
|
465
|
+
this.locals.clear();
|
|
466
|
+
this.preview = null;
|
|
467
|
+
this.cameraPublished = false;
|
|
468
|
+
this.cameraClaimed = false;
|
|
469
|
+
this.micCid = null;
|
|
470
|
+
this.cameraSender = null;
|
|
471
|
+
// 排着队的开关回来时认出代数不对,自己收摊(syncCameraCapture)。
|
|
472
|
+
this.cameraToggle = Promise.resolve();
|
|
473
|
+
// 还在起的预览作废:它回来时认出代数不对,自己把流放掉(openPreview)。
|
|
474
|
+
this.previewOpening = null;
|
|
475
|
+
this.closeGeneration += 1;
|
|
476
|
+
this.pendingCandidates.pub = [];
|
|
477
|
+
this.pendingCandidates.sub = [];
|
|
478
|
+
this.pub?.close();
|
|
479
|
+
this.sub?.close();
|
|
480
|
+
this.pub = null;
|
|
481
|
+
this.sub = null;
|
|
482
|
+
this.events = null;
|
|
483
|
+
}
|
|
484
|
+
requirePub() {
|
|
485
|
+
if (this.pub === null)
|
|
486
|
+
throw new RtcError(ErrorCode.invalidState, { cause: new Error('媒体层未打开') });
|
|
487
|
+
return this.pub;
|
|
488
|
+
}
|
|
489
|
+
requireSub() {
|
|
490
|
+
if (this.sub === null)
|
|
491
|
+
throw new RtcError(ErrorCode.invalidState, { cause: new Error('媒体层未打开') });
|
|
492
|
+
return this.sub;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function isPermissionError(cause) {
|
|
496
|
+
return cause instanceof Error && (cause.name === 'NotAllowedError' || cause.name === 'SecurityError');
|
|
497
|
+
}
|
|
498
|
+
/** previewInfo 把预览翻成 LocalTrackInfo。cid 是**第一条**采集轨道的 id(协议 §3.2)。 */
|
|
499
|
+
function previewInfo(cid) {
|
|
500
|
+
return { cid, kind: 'video', source: 'camera' };
|
|
501
|
+
}
|
|
502
|
+
//# sourceMappingURL=webrtcAdapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webrtcAdapter.js","sourceRoot":"","sources":["../../src/media/webrtcAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* checkDeviceId 校验 `device_id`。
|
|
3
|
+
*
|
|
4
|
+
* 宿主也可以自己先调它验一遍生成的 id,不必等到 `login()`。
|
|
5
|
+
*/
|
|
6
|
+
export declare function checkDeviceId(deviceId: string): void;
|
|
7
|
+
/**
|
|
8
|
+
* checkRoomId 校验宿主指定的 `room_id`。
|
|
9
|
+
*
|
|
10
|
+
* 服务端分配的房间号必然合规,这道检查是给 `joinRoom(roomId)` 这种**宿主自己
|
|
11
|
+
* 指定房间号**的用法的——拿群名当房间号是最常见的写法,而群名里什么都有。
|
|
12
|
+
*/
|
|
13
|
+
export declare function checkRoomId(roomId: string): void;
|
|
14
|
+
//# sourceMappingURL=protocolId.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocolId.d.ts","sourceRoot":"","sources":["../src/protocolId.ts"],"names":[],"mappings":"AAsGA;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAMpD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAEhD"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { byteLength } from './bytes.js';
|
|
2
|
+
import { ErrorCode, RtcError } from './errors.js';
|
|
3
|
+
import { logger } from './logger.js';
|
|
4
|
+
/**
|
|
5
|
+
* §2.5 那一列 id 的入参校验:非空、≤64 **字节**、charset `[A-Za-z0-9_-]`。
|
|
6
|
+
*
|
|
7
|
+
* # 为什么要在端上拦一道
|
|
8
|
+
*
|
|
9
|
+
* **不拦的症状是「登录失败,没有下文」**:服务端回 1004,客户端握手失败,
|
|
10
|
+
* 而服务端那句说得很清楚的「device_id 只允许 [A-Za-z0-9_-]」**到不了宿主手里**——
|
|
11
|
+
* 宿主看到的只有一个 `bad_params`。安卓真机上踩过一次(`Build.MODEL` 是
|
|
12
|
+
* `Pixel 2 XL`,带空格),查了一轮才定位到是机型名。
|
|
13
|
+
*
|
|
14
|
+
* Web 上最常见的错法是拿浏览器信息拼 id:`navigator.platform` 是 `MacIntel` 没事,
|
|
15
|
+
* 但 `navigator.userAgent` 里到处是空格、斜杠和括号,UUID 带的连字符倒是合规的。
|
|
16
|
+
*
|
|
17
|
+
* # 为什么是通用的 id 校验,不是 device_id 专用
|
|
18
|
+
*
|
|
19
|
+
* §2.5 里 `device_id` / `room_id` / `cid` 是**同一条约束**,踩的也是同一个坑:
|
|
20
|
+
* 宿主指定的值不合规 → 服务端 1004 → 宿主只看到 `bad_params`。一个字段写一份
|
|
21
|
+
* 校验的话,下一个字段会把这一轮排查重新踩一遍——`joinRoom(roomId)` 就是现成的
|
|
22
|
+
* 下一个(宿主拿群名当房间号,中文和空格一起来)。
|
|
23
|
+
*
|
|
24
|
+
* # 为什么这里要记日志
|
|
25
|
+
*
|
|
26
|
+
* `cause` 按 `errors.ts` 的约定**只进日志、不进 UI**,所以光把原因塞进 `cause`
|
|
27
|
+
* 并不能把它送到宿主手里——宿主的 catch 里 `String(err)` 只有那句固定短语。
|
|
28
|
+
* 这条 `logger.warn` 才是原因唯一的落点:诊断环形缓冲不受 `setLogLevel` 影响,
|
|
29
|
+
* 用户点「报告问题」调 `exportDiagnostics()` 时它还在。
|
|
30
|
+
*
|
|
31
|
+
* # 只校验,不改写
|
|
32
|
+
*
|
|
33
|
+
* 这些 id 要求**跨重启稳定**,SDK 悄悄替宿主改掉,宿主自己那套设备管理
|
|
34
|
+
* (设备列表、注销设备、顶号)就跟服务端对不上账了。清洗是宿主的事。
|
|
35
|
+
*
|
|
36
|
+
* 真要清洗,**别用「删掉非法字符」那种做法**:`MI 8` 与 `MI8` 是两款不同的机器,
|
|
37
|
+
* 删完就撞成同一个 device_id,而撞号的后果是两台设备互相顶号、轮流把对方踢下线。
|
|
38
|
+
* 换成 `-` 才不会。
|
|
39
|
+
*/
|
|
40
|
+
/** 协议 §2.5:这一列 id 都是 ≤64 **字节**(不是字符)。 */
|
|
41
|
+
const MAX_ID_BYTES = 64;
|
|
42
|
+
/**
|
|
43
|
+
* 用 `+` 而不是 `*`:空串该由这条正则自己拒掉。
|
|
44
|
+
*
|
|
45
|
+
* 写成 `*` 的话空串能过 charset 这关,于是「先查空、再查 charset」的**顺序**
|
|
46
|
+
* 变成了正确性的一部分——下一个人合并两个分支或者换个地方复用它,空串就悄悄放行了。
|
|
47
|
+
*/
|
|
48
|
+
const ALLOWED = /^[A-Za-z0-9_-]+$/;
|
|
49
|
+
/**
|
|
50
|
+
* checkProtocolId 校验一个 §2.5 的 id,不合规就记一条日志再抛 `RtcError(bad_params)`。
|
|
51
|
+
*
|
|
52
|
+
* 抛的码**和服务端拒绝时是同一个 1004**——宿主那套按 code 分支的错误处理不用为
|
|
53
|
+
* 「本地拦下的」和「服务端拒的」写两遍,具体哪里不对看日志。
|
|
54
|
+
*
|
|
55
|
+
* `value` 收 `unknown` 而不是 `string`:这是**公开导出的入口**,宿主可能是 JS,
|
|
56
|
+
* 也可能把一个没取到的配置项直接传进来。详见下面第一道检查。
|
|
57
|
+
*/
|
|
58
|
+
function checkProtocolId(field, value, hint) {
|
|
59
|
+
/*
|
|
60
|
+
**先挡非字符串**,不然 `undefined` 会一路放行到线路上:
|
|
61
|
+
`undefined === ''` 是 false;`TextEncoder.encode(undefined)` 按规范把参数
|
|
62
|
+
默认成 `''`,算出来 0 字节;而 `ALLOWED.test(undefined)` 会先把它转成字符串
|
|
63
|
+
`'undefined'`——正好全是合规字符。三关全过,然后服务端拒掉,
|
|
64
|
+
症状又回到这个模块存在的理由本身。
|
|
65
|
+
*/
|
|
66
|
+
if (typeof value !== 'string') {
|
|
67
|
+
throw reject(field, value, `${field} 必须是字符串,得到 ${describe(value)}(协议 §2.5)`);
|
|
68
|
+
}
|
|
69
|
+
if (value === '') {
|
|
70
|
+
throw reject(field, value, `${field} 不能为空(协议 §2.5)`);
|
|
71
|
+
}
|
|
72
|
+
const bytes = byteLength(value);
|
|
73
|
+
if (bytes > MAX_ID_BYTES) {
|
|
74
|
+
throw reject(field, value, `${field} 长 ${bytes} 字节,上限 ${MAX_ID_BYTES}(协议 §2.5)`);
|
|
75
|
+
}
|
|
76
|
+
if (!ALLOWED.test(value)) {
|
|
77
|
+
// 逐个码点找,好把「到底是哪个字符」说出来——`[...str]` 按码点切,
|
|
78
|
+
// emoji 那种四字节的不会被劈成两个半拉代理对。
|
|
79
|
+
const bad = [...value].find((ch) => !ALLOWED.test(ch));
|
|
80
|
+
throw reject(field, value, `${field} 只允许 [A-Za-z0-9_-],出现了 '${String(bad)}'(协议 §2.5)。${hint}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** describe 给非字符串取一个能读的类型名——`typeof null` 是 'object',单独说。 */
|
|
84
|
+
function describe(value) {
|
|
85
|
+
return value === null ? 'null' : typeof value;
|
|
86
|
+
}
|
|
87
|
+
/** reject 记一条日志再造错误。**日志是原因唯一到得了宿主的通道**,见文件头。 */
|
|
88
|
+
function reject(field, value, reason) {
|
|
89
|
+
logger.warn('入参 id 不合规,就地拒掉', { field, value: String(value), reason });
|
|
90
|
+
return new RtcError(ErrorCode.badParams, { cause: new Error(reason) });
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* checkDeviceId 校验 `device_id`。
|
|
94
|
+
*
|
|
95
|
+
* 宿主也可以自己先调它验一遍生成的 id,不必等到 `login()`。
|
|
96
|
+
*/
|
|
97
|
+
export function checkDeviceId(deviceId) {
|
|
98
|
+
checkProtocolId('device_id', deviceId, '拿 userAgent 之类拼 id 的话记得先清洗——里面空格和括号是常态');
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* checkRoomId 校验宿主指定的 `room_id`。
|
|
102
|
+
*
|
|
103
|
+
* 服务端分配的房间号必然合规,这道检查是给 `joinRoom(roomId)` 这种**宿主自己
|
|
104
|
+
* 指定房间号**的用法的——拿群名当房间号是最常见的写法,而群名里什么都有。
|
|
105
|
+
*/
|
|
106
|
+
export function checkRoomId(roomId) {
|
|
107
|
+
checkProtocolId('room_id', roomId, '别直接拿群名当房间号——中文、空格都会被拒');
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=protocolId.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocolId.js","sourceRoot":"","sources":["../src/protocolId.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,0CAA0C;AAC1C,MAAM,YAAY,GAAG,EAAE,CAAC;AAExB;;;;;GAKG;AACH,MAAM,OAAO,GAAG,kBAAkB,CAAC;AAEnC;;;;;;;;GAQG;AACH,SAAS,eAAe,CAAC,KAAa,EAAE,KAAc,EAAE,IAAY;IAClE;;;;;;MAME;IACF,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,KAAK,cAAc,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC/E,CAAC;IACD,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QACjB,MAAM,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,KAAK,gBAAgB,CAAC,CAAC;IACvD,CAAC;IACD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAChC,IAAI,KAAK,GAAG,YAAY,EAAE,CAAC;QACzB,MAAM,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,KAAK,MAAM,KAAK,UAAU,YAAY,WAAW,CAAC,CAAC;IACnF,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,yCAAyC;QACzC,4BAA4B;QAC5B,MAAM,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACvD,MAAM,MAAM,CACV,KAAK,EACL,KAAK,EACL,GAAG,KAAK,2BAA2B,MAAM,CAAC,GAAG,CAAC,cAAc,IAAI,EAAE,CACnE,CAAC;IACJ,CAAC;AACH,CAAC;AAED,6DAA6D;AAC7D,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC;AAChD,CAAC;AAED,iDAAiD;AACjD,SAAS,MAAM,CAAC,KAAa,EAAE,KAAc,EAAE,MAAc;IAC3D,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IACvE,OAAO,IAAI,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AACzE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,eAAe,CACb,WAAW,EACX,QAAQ,EACR,wCAAwC,CACzC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,uBAAuB,CAAC,CAAC;AAC9D,CAAC"}
|