im-rtc-call-engine 1.0.0 → 2.1.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/dist/browserSignals.d.ts +27 -0
- package/dist/browserSignals.d.ts.map +1 -0
- package/dist/browserSignals.js +34 -0
- package/dist/browserSignals.js.map +1 -0
- package/dist/callGuards.d.ts +35 -0
- package/dist/callGuards.d.ts.map +1 -0
- package/dist/callGuards.js +66 -0
- package/dist/callGuards.js.map +1 -0
- package/dist/callHistory.d.ts +50 -0
- package/dist/callHistory.d.ts.map +1 -0
- package/dist/callHistory.js +100 -0
- package/dist/callHistory.js.map +1 -0
- package/dist/callOptions.d.ts +14 -0
- package/dist/callOptions.d.ts.map +1 -1
- package/dist/callOptions.js +21 -0
- package/dist/callOptions.js.map +1 -1
- package/dist/debugToken.d.ts +29 -0
- package/dist/debugToken.d.ts.map +1 -0
- package/dist/debugToken.js +73 -0
- package/dist/debugToken.js.map +1 -0
- package/dist/engine.d.ts +88 -60
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +141 -178
- package/dist/engine.js.map +1 -1
- package/dist/engineBus.d.ts.map +1 -1
- package/dist/engineBus.js +1 -0
- package/dist/engineBus.js.map +1 -1
- package/dist/engineSession.d.ts +49 -0
- package/dist/engineSession.d.ts.map +1 -0
- package/dist/engineSession.js +97 -0
- package/dist/engineSession.js.map +1 -0
- package/dist/events.d.ts +48 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +2 -0
- package/dist/events.js.map +1 -1
- package/dist/frameLoop.d.ts +48 -5
- package/dist/frameLoop.d.ts.map +1 -1
- package/dist/frameLoop.js +205 -29
- package/dist/frameLoop.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/media/captureStream.d.ts +9 -0
- package/dist/media/captureStream.d.ts.map +1 -0
- package/dist/media/captureStream.js +20 -0
- package/dist/media/captureStream.js.map +1 -0
- package/dist/media/engineMediaApi.d.ts +24 -3
- package/dist/media/engineMediaApi.d.ts.map +1 -1
- package/dist/media/engineMediaApi.js +63 -27
- package/dist/media/engineMediaApi.js.map +1 -1
- package/dist/media/mediaBridge.d.ts +6 -1
- package/dist/media/mediaBridge.d.ts.map +1 -1
- package/dist/media/mediaBridge.js +18 -1
- package/dist/media/mediaBridge.js.map +1 -1
- package/dist/media/remoteAudio.d.ts +65 -0
- package/dist/media/remoteAudio.d.ts.map +1 -0
- package/dist/media/remoteAudio.js +83 -0
- package/dist/media/remoteAudio.js.map +1 -0
- package/dist/media/videoSender.d.ts +21 -0
- package/dist/media/videoSender.d.ts.map +1 -0
- package/dist/media/videoSender.js +74 -0
- package/dist/media/videoSender.js.map +1 -0
- package/dist/media/viewRegistry.d.ts +11 -3
- package/dist/media/viewRegistry.d.ts.map +1 -1
- package/dist/media/viewRegistry.js +35 -5
- package/dist/media/viewRegistry.js.map +1 -1
- package/dist/media/webrtcAdapter.d.ts +0 -31
- package/dist/media/webrtcAdapter.d.ts.map +1 -1
- package/dist/media/webrtcAdapter.js +10 -88
- package/dist/media/webrtcAdapter.js.map +1 -1
- package/dist/signaling/connection.d.ts +36 -0
- package/dist/signaling/connection.d.ts.map +1 -1
- package/dist/signaling/connection.js +89 -2
- package/dist/signaling/connection.js.map +1 -1
- package/dist/signaling/enums.d.ts +15 -0
- package/dist/signaling/enums.d.ts.map +1 -1
- package/dist/signaling/enums.js +17 -0
- package/dist/signaling/enums.js.map +1 -1
- package/dist/signaling/envelope.d.ts +13 -1
- package/dist/signaling/envelope.d.ts.map +1 -1
- package/dist/signaling/envelope.js +15 -3
- package/dist/signaling/envelope.js.map +1 -1
- package/dist/signaling/frames.call.d.ts +14 -0
- package/dist/signaling/frames.call.d.ts.map +1 -1
- package/dist/signaling/frames.call.js +8 -0
- package/dist/signaling/frames.call.js.map +1 -1
- package/dist/signaling/frames.room.d.ts +6 -4
- package/dist/signaling/frames.room.d.ts.map +1 -1
- package/dist/signaling/frames.room.js +10 -4
- package/dist/signaling/frames.room.js.map +1 -1
- package/dist/signaling/frames.sys.d.ts +5 -1
- package/dist/signaling/frames.sys.d.ts.map +1 -1
- package/dist/signaling/frames.sys.js +5 -1
- package/dist/signaling/frames.sys.js.map +1 -1
- package/dist/signaling/heartbeat.js +1 -1
- package/dist/signaling/heartbeat.js.map +1 -1
- package/dist/signaling/networkProbe.d.ts +31 -0
- package/dist/signaling/networkProbe.d.ts.map +1 -0
- package/dist/signaling/networkProbe.js +47 -0
- package/dist/signaling/networkProbe.js.map +1 -0
- package/dist/signaling/oneShotTimer.d.ts +30 -0
- package/dist/signaling/oneShotTimer.d.ts.map +1 -0
- package/dist/signaling/oneShotTimer.js +44 -0
- package/dist/signaling/oneShotTimer.js.map +1 -0
- package/dist/signaling/reconnector.d.ts +17 -1
- package/dist/signaling/reconnector.d.ts.map +1 -1
- package/dist/signaling/reconnector.js +48 -14
- package/dist/signaling/reconnector.js.map +1 -1
- package/dist/signaling/resumeDeadline.d.ts +1 -1
- package/dist/signaling/resumeDeadline.d.ts.map +1 -1
- package/dist/signaling/resumeDeadline.js +7 -10
- package/dist/signaling/resumeDeadline.js.map +1 -1
- package/dist/state/callMachine.d.ts +7 -0
- package/dist/state/callMachine.d.ts.map +1 -1
- package/dist/state/callMachine.js +16 -7
- package/dist/state/callMachine.js.map +1 -1
- package/dist/state/callRecv.d.ts.map +1 -1
- package/dist/state/callRecv.js +9 -1
- package/dist/state/callRecv.js.map +1 -1
- package/dist/state/engineMachine.d.ts.map +1 -1
- package/dist/state/engineMachine.js +26 -6
- package/dist/state/engineMachine.js.map +1 -1
- package/dist/state/roomMachine.d.ts +10 -2
- package/dist/state/roomMachine.d.ts.map +1 -1
- package/dist/state/roomMachine.js +87 -15
- package/dist/state/roomMachine.js.map +1 -1
- package/dist/state/roomPaging.d.ts +81 -0
- package/dist/state/roomPaging.d.ts.map +1 -0
- package/dist/state/roomPaging.js +202 -0
- package/dist/state/roomPaging.js.map +1 -0
- package/dist/state/roomRecv.d.ts.map +1 -1
- package/dist/state/roomRecv.js +17 -7
- package/dist/state/roomRecv.js.map +1 -1
- package/dist/state/types.d.ts +12 -0
- package/dist/state/types.d.ts.map +1 -1
- package/dist/state/types.js.map +1 -1
- package/dist/state/unsubscribeTimers.d.ts +29 -0
- package/dist/state/unsubscribeTimers.d.ts.map +1 -0
- package/dist/state/unsubscribeTimers.js +55 -0
- package/dist/state/unsubscribeTimers.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浏览器替宿主盯两件事:标签页回到前台(`visibilitychange`)、网络变了(`online` 与
|
|
3
|
+
* `navigator.connection` 的 `change`),转成 `Connection.setAppForeground` / `notifyNetworkChanged`。
|
|
4
|
+
*
|
|
5
|
+
* iOS / Android 由 Kit 喂这两个信号;Web 没有「Kit 起来了」这个时机,而这些事件在浏览器里是全局的,
|
|
6
|
+
* 所以登录时由 `EngineSession` 自己挂上、登出摘掉——**宿主什么都不用写**。宿主自己再调
|
|
7
|
+
* `CallEngine.setAppForeground` / `notifyNetworkChanged` 也无妨:探测一次只探一个,立刻重连两次至少隔 2 秒。
|
|
8
|
+
*
|
|
9
|
+
* 不在浏览器里(Node 单测、SSR)就什么都不挂。
|
|
10
|
+
*/
|
|
11
|
+
/** BrowserSignalTarget 是收信号的那一方(`Connection`)。 */
|
|
12
|
+
export interface BrowserSignalTarget {
|
|
13
|
+
setAppForeground(foreground: boolean): void;
|
|
14
|
+
notifyNetworkChanged(): void;
|
|
15
|
+
}
|
|
16
|
+
/** BrowserEnv 是要听的三个事件源,单测可注入。 */
|
|
17
|
+
export interface BrowserEnv {
|
|
18
|
+
readonly document?: (EventTarget & {
|
|
19
|
+
readonly visibilityState: string;
|
|
20
|
+
}) | undefined;
|
|
21
|
+
readonly window?: EventTarget | undefined;
|
|
22
|
+
/** `navigator.connection`(Chrome 系有,Safari / Firefox 没有)。 */
|
|
23
|
+
readonly connection?: EventTarget | undefined;
|
|
24
|
+
}
|
|
25
|
+
/** watchBrowserSignals 挂上监听,返回摘除函数。 */
|
|
26
|
+
export declare function watchBrowserSignals(target: BrowserSignalTarget, env?: BrowserEnv): () => void;
|
|
27
|
+
//# sourceMappingURL=browserSignals.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browserSignals.d.ts","sourceRoot":"","sources":["../src/browserSignals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,kDAAkD;AAClD,MAAM,WAAW,mBAAmB;IAClC,gBAAgB,CAAC,UAAU,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5C,oBAAoB,IAAI,IAAI,CAAC;CAC9B;AAED,kCAAkC;AAClC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,WAAW,GAAG;QAAE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,SAAS,CAAC;IACrF,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,6DAA6D;IAC7D,QAAQ,CAAC,UAAU,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAC/C;AAWD,uCAAuC;AACvC,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,EAAE,GAAG,GAAE,UAAyB,GAAG,MAAM,IAAI,CAe3G"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浏览器替宿主盯两件事:标签页回到前台(`visibilitychange`)、网络变了(`online` 与
|
|
3
|
+
* `navigator.connection` 的 `change`),转成 `Connection.setAppForeground` / `notifyNetworkChanged`。
|
|
4
|
+
*
|
|
5
|
+
* iOS / Android 由 Kit 喂这两个信号;Web 没有「Kit 起来了」这个时机,而这些事件在浏览器里是全局的,
|
|
6
|
+
* 所以登录时由 `EngineSession` 自己挂上、登出摘掉——**宿主什么都不用写**。宿主自己再调
|
|
7
|
+
* `CallEngine.setAppForeground` / `notifyNetworkChanged` 也无妨:探测一次只探一个,立刻重连两次至少隔 2 秒。
|
|
8
|
+
*
|
|
9
|
+
* 不在浏览器里(Node 单测、SSR)就什么都不挂。
|
|
10
|
+
*/
|
|
11
|
+
function defaultEnv() {
|
|
12
|
+
const g = globalThis;
|
|
13
|
+
return { document: g.document, window: g.window, connection: g.navigator?.connection };
|
|
14
|
+
}
|
|
15
|
+
/** watchBrowserSignals 挂上监听,返回摘除函数。 */
|
|
16
|
+
export function watchBrowserSignals(target, env = defaultEnv()) {
|
|
17
|
+
const removers = [];
|
|
18
|
+
const listen = (source, type, handler) => {
|
|
19
|
+
if (source === undefined || typeof source.addEventListener !== 'function')
|
|
20
|
+
return;
|
|
21
|
+
source.addEventListener(type, handler);
|
|
22
|
+
removers.push(() => source.removeEventListener(type, handler));
|
|
23
|
+
};
|
|
24
|
+
const doc = env.document;
|
|
25
|
+
listen(doc, 'visibilitychange', () => target.setAppForeground(doc?.visibilityState === 'visible'));
|
|
26
|
+
// 断网期间不报:没网可连。`online` 就是「从没网恢复到有网」。
|
|
27
|
+
listen(env.window, 'online', () => target.notifyNetworkChanged());
|
|
28
|
+
listen(env.connection, 'change', () => target.notifyNetworkChanged());
|
|
29
|
+
return () => {
|
|
30
|
+
for (const remove of removers.splice(0))
|
|
31
|
+
remove();
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=browserSignals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browserSignals.js","sourceRoot":"","sources":["../src/browserSignals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAgBH,SAAS,UAAU;IACjB,MAAM,CAAC,GAAG,UAIT,CAAC;IACF,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,SAAS,EAAE,UAAU,EAAE,CAAC;AACzF,CAAC;AAED,uCAAuC;AACvC,MAAM,UAAU,mBAAmB,CAAC,MAA2B,EAAE,MAAkB,UAAU,EAAE;IAC7F,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,MAAM,MAAM,GAAG,CAAC,MAA+B,EAAE,IAAY,EAAE,OAAmB,EAAQ,EAAE;QAC1F,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,MAAM,CAAC,gBAAgB,KAAK,UAAU;YAAE,OAAO;QAClF,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACvC,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACjE,CAAC,CAAC;IACF,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC;IACzB,MAAM,CAAC,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG,EAAE,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC;IACnG,qCAAqC;IACrC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,oBAAoB,EAAE,CAAC,CAAC;IAClE,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,oBAAoB,EAAE,CAAC,CAAC;IACtE,OAAO,GAAS,EAAE;QAChB,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;YAAE,MAAM,EAAE,CAAC;IACpD,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { EngineBus } from './engineBus.js';
|
|
2
|
+
import { RtcError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* rejectsSelf 挡住「名单里有自己」:返回要交给调用方的 `1004`,没问题时返回 null。
|
|
5
|
+
*
|
|
6
|
+
* 服务端会以 `1004 bad_params` 拒掉("callee_ids 不能含主叫自己"),但那条链路上的
|
|
7
|
+
* 失败很难看懂:界面已经乐观地进了「正在呼叫…」,而错误只是一条没头没尾的 1004。
|
|
8
|
+
* (实测撞过:Demo 的群呼默认名单里正好有登录的那个人。)
|
|
9
|
+
*
|
|
10
|
+
* **一份实现供 call 与 inviteMore 共用**——两处各写一遍的话,改了一处忘了另一处,
|
|
11
|
+
* 就又是一个「同一条规则在一处成立、另一处不成立」。
|
|
12
|
+
*/
|
|
13
|
+
export declare function rejectsSelf(myUid: string, calleeIds: readonly string[], what: string, forType: string): RtcError | null;
|
|
14
|
+
/**
|
|
15
|
+
* rejectsBadCallOptions 挡住超限的 `chatGroupId` / `userData`:返回要交给调用方的 `1004`,没问题时返回 null。
|
|
16
|
+
*
|
|
17
|
+
* 服务端会以 `1004 bad_params` 拒掉,但那条链路上主叫已经乐观地进了「正在呼叫…」,
|
|
18
|
+
* 错误只是一条没头没尾的 1004——与 {@link rejectsSelf} 同一个理由,本地先拦,
|
|
19
|
+
* 走同一个出口(HOST_INTEGRATION_DESIGN §3.3)。
|
|
20
|
+
*/
|
|
21
|
+
export declare function rejectsBadCallOptions(chatGroupId: string, userData: string): RtcError | null;
|
|
22
|
+
/**
|
|
23
|
+
* emitLocallyRejectedCall 给「`call()` 被本地拒掉」一个出口:抛一条 `callEnd(error)`。
|
|
24
|
+
*
|
|
25
|
+
* 调用方(uikit / 宿主)在调 `call()` 之前就已经切到「正在呼叫…」了——
|
|
26
|
+
* 这是对的,不然按下去几百毫秒没反应。但只交回一个错误,
|
|
27
|
+
* 靠事件驱动的界面不知道该退回哪儿:实测三人测试里 carol 卡在「正在呼叫…」,
|
|
28
|
+
* 连点五次挂断收到五个 2005(状态机是 idle,没有 call 可挂),
|
|
29
|
+
* 除了刷新页面没有别的出路。
|
|
30
|
+
*
|
|
31
|
+
* `callEnd` 是所有结束分支的唯一出口(设计 §7.5),
|
|
32
|
+
* 这一条与「服务端拒了 invite」(call_failed)走同一个出口,界面只认它。
|
|
33
|
+
*/
|
|
34
|
+
export declare function emitLocallyRejectedCall(bus: EngineBus): void;
|
|
35
|
+
//# sourceMappingURL=callGuards.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callGuards.d.ts","sourceRoot":"","sources":["../src/callGuards.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAa,QAAQ,EAAE,MAAM,aAAa,CAAC;AAclD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,QAAQ,GAAG,IAAI,CAIjB;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAM5F;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAO5D"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { byteLength } from './bytes.js';
|
|
2
|
+
import { violatesCallOptionLimits } from './callOptions.js';
|
|
3
|
+
import { ErrorCode, RtcError } from './errors.js';
|
|
4
|
+
import { logger } from './logger.js';
|
|
5
|
+
import { CallEndReason } from './reasons.js';
|
|
6
|
+
/*
|
|
7
|
+
发起类动作(`call` / `inviteMore`)上线路**之前**的本地关卡。
|
|
8
|
+
|
|
9
|
+
从 `engine.ts` 里单拎出来:门面只该留公开签名与给宿主看的文档,
|
|
10
|
+
「哪些参数本地先拦、拦了走哪个出口」是一段自成一体的规则,放在门面里只是让那个文件贴着体量红线。
|
|
11
|
+
判断本身是纯函数的那部分在 `callOptions.ts`;这里管的是日志与「拒了之后给界面的出口」。
|
|
12
|
+
拒绝本身**交给调用方**(门面 throw 返回的错误),不发 error 事件——一次失败只从一个出口报
|
|
13
|
+
(server `docs/design/ACTION_RESULT_DESIGN.md` R3)。
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* rejectsSelf 挡住「名单里有自己」:返回要交给调用方的 `1004`,没问题时返回 null。
|
|
17
|
+
*
|
|
18
|
+
* 服务端会以 `1004 bad_params` 拒掉("callee_ids 不能含主叫自己"),但那条链路上的
|
|
19
|
+
* 失败很难看懂:界面已经乐观地进了「正在呼叫…」,而错误只是一条没头没尾的 1004。
|
|
20
|
+
* (实测撞过:Demo 的群呼默认名单里正好有登录的那个人。)
|
|
21
|
+
*
|
|
22
|
+
* **一份实现供 call 与 inviteMore 共用**——两处各写一遍的话,改了一处忘了另一处,
|
|
23
|
+
* 就又是一个「同一条规则在一处成立、另一处不成立」。
|
|
24
|
+
*/
|
|
25
|
+
export function rejectsSelf(myUid, calleeIds, what, forType) {
|
|
26
|
+
if (myUid === '' || !calleeIds.includes(myUid))
|
|
27
|
+
return null;
|
|
28
|
+
logger.warn(`${what}名单里含自己,已就地拒掉`, { uid: myUid });
|
|
29
|
+
return new RtcError(ErrorCode.badParams, { forType });
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* rejectsBadCallOptions 挡住超限的 `chatGroupId` / `userData`:返回要交给调用方的 `1004`,没问题时返回 null。
|
|
33
|
+
*
|
|
34
|
+
* 服务端会以 `1004 bad_params` 拒掉,但那条链路上主叫已经乐观地进了「正在呼叫…」,
|
|
35
|
+
* 错误只是一条没头没尾的 1004——与 {@link rejectsSelf} 同一个理由,本地先拦,
|
|
36
|
+
* 走同一个出口(HOST_INTEGRATION_DESIGN §3.3)。
|
|
37
|
+
*/
|
|
38
|
+
export function rejectsBadCallOptions(chatGroupId, userData) {
|
|
39
|
+
if (!violatesCallOptionLimits(chatGroupId, userData))
|
|
40
|
+
return null;
|
|
41
|
+
logger.warn('chatGroupId / userData 超限,已就地拒掉', {
|
|
42
|
+
chatGroupId, userDataBytes: byteLength(userData),
|
|
43
|
+
});
|
|
44
|
+
return new RtcError(ErrorCode.badParams, { forType: 'call.invite' });
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* emitLocallyRejectedCall 给「`call()` 被本地拒掉」一个出口:抛一条 `callEnd(error)`。
|
|
48
|
+
*
|
|
49
|
+
* 调用方(uikit / 宿主)在调 `call()` 之前就已经切到「正在呼叫…」了——
|
|
50
|
+
* 这是对的,不然按下去几百毫秒没反应。但只交回一个错误,
|
|
51
|
+
* 靠事件驱动的界面不知道该退回哪儿:实测三人测试里 carol 卡在「正在呼叫…」,
|
|
52
|
+
* 连点五次挂断收到五个 2005(状态机是 idle,没有 call 可挂),
|
|
53
|
+
* 除了刷新页面没有别的出路。
|
|
54
|
+
*
|
|
55
|
+
* `callEnd` 是所有结束分支的唯一出口(设计 §7.5),
|
|
56
|
+
* 这一条与「服务端拒了 invite」(call_failed)走同一个出口,界面只认它。
|
|
57
|
+
*/
|
|
58
|
+
export function emitLocallyRejectedCall(bus) {
|
|
59
|
+
bus.emit('callEnd', {
|
|
60
|
+
callId: '',
|
|
61
|
+
reason: CallEndReason.error,
|
|
62
|
+
durationSec: 0,
|
|
63
|
+
endedBy: '',
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=callGuards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callGuards.js","sourceRoot":"","sources":["../src/callGuards.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAE5D,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C;;;;;;;;EAQE;AAEF;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CACzB,KAAa,EACb,SAA4B,EAC5B,IAAY,EACZ,OAAe;IAEf,IAAI,KAAK,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5D,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,cAAc,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;IACnD,OAAO,IAAI,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;AACxD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,WAAmB,EAAE,QAAgB;IACzE,IAAI,CAAC,wBAAwB,CAAC,WAAW,EAAE,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAClE,MAAM,CAAC,IAAI,CAAC,iCAAiC,EAAE;QAC7C,WAAW,EAAE,aAAa,EAAE,UAAU,CAAC,QAAQ,CAAC;KACjD,CAAC,CAAC;IACH,OAAO,IAAI,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;AACvE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,uBAAuB,CAAC,GAAc;IACpD,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE;QAClB,MAAM,EAAE,EAAE;QACV,MAAM,EAAE,aAAa,CAAC,KAAK;QAC3B,WAAW,EAAE,CAAC;QACd,OAAO,EAAE,EAAE;KACZ,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** 通话记录里的一位成员。`state` 是这位成员在这通电话里的结局,原样透传。 */
|
|
2
|
+
export interface CallHistoryMember {
|
|
3
|
+
readonly uid: string;
|
|
4
|
+
readonly state: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* 一条通话记录,字段与服务端 `GET /v1/calls` 一一对应(camelCase)。
|
|
8
|
+
*
|
|
9
|
+
* `reason` 是通话的最终结局(`hangup` / `cancel` / `reject` / `no_answer`…),**不分角色**:
|
|
10
|
+
* 要显示「已取消」还是「对方已取消」,用 `caller` 与自己的 `uid` 比出角色再定文案。
|
|
11
|
+
*/
|
|
12
|
+
export interface CallHistoryRecord {
|
|
13
|
+
readonly callId: string;
|
|
14
|
+
readonly roomId: string;
|
|
15
|
+
readonly caller: string;
|
|
16
|
+
/** `audio` 或 `video`。 */
|
|
17
|
+
readonly mediaType: string;
|
|
18
|
+
readonly isGroup: boolean;
|
|
19
|
+
readonly reason: string;
|
|
20
|
+
readonly endedBy: string;
|
|
21
|
+
readonly durationSec: number;
|
|
22
|
+
readonly startedAtMs: number;
|
|
23
|
+
readonly connectedAtMs: number;
|
|
24
|
+
readonly endedAtMs: number;
|
|
25
|
+
readonly userData: string;
|
|
26
|
+
readonly chatGroupId: string;
|
|
27
|
+
readonly members: readonly CallHistoryMember[];
|
|
28
|
+
}
|
|
29
|
+
/** 一页通话记录(按发起时间倒序)。`nextCursor` 为 `null` 表示已经到底。 */
|
|
30
|
+
export interface CallHistoryPage {
|
|
31
|
+
readonly records: readonly CallHistoryRecord[];
|
|
32
|
+
readonly nextCursor: number | null;
|
|
33
|
+
}
|
|
34
|
+
export interface FetchCallHistoryOptions {
|
|
35
|
+
/** 每页条数,夹在 1..200,默认 20。 */
|
|
36
|
+
readonly limit?: number;
|
|
37
|
+
/** 首页不传;下一页传上一页的 `nextCursor`。 */
|
|
38
|
+
readonly cursor?: number;
|
|
39
|
+
}
|
|
40
|
+
/** 服务端每页上限(`maxCallLimit`)。超过它服务端也只回这么多,「到底」的判据就不成立了,所以本地先夹住。 */
|
|
41
|
+
export declare const MAX_CALL_HISTORY_LIMIT = 200;
|
|
42
|
+
export declare const DEFAULT_CALL_HISTORY_LIMIT = 20;
|
|
43
|
+
/** 信令地址推出 REST 根:`ws→http`、`wss→https`,去掉末尾的 `/v1/ws`。推不出返回 `null`。 */
|
|
44
|
+
export declare function restBaseUrl(signalingUrl: string): string | null;
|
|
45
|
+
export declare function callHistoryUrl(signalingUrl: string, limit: number, cursor?: number): string | null;
|
|
46
|
+
/** 状态码与应答体 → 一页记录;出错抛 `RtcError`。 */
|
|
47
|
+
export declare function parseCallHistory(status: number, body: unknown, limit: number): CallHistoryPage;
|
|
48
|
+
/** 发请求并解析。`fetchImpl` 可注入,单测不碰网络。 */
|
|
49
|
+
export declare function fetchCallHistory(signalingUrl: string, token: string, options?: FetchCallHistoryOptions, fetchImpl?: typeof fetch): Promise<CallHistoryPage>;
|
|
50
|
+
//# sourceMappingURL=callHistory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callHistory.d.ts","sourceRoot":"","sources":["../src/callHistory.ts"],"names":[],"mappings":"AAYA,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,yBAAyB;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAChD;AAED,oDAAoD;AACpD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC/C,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,uBAAuB;IACtC,4BAA4B;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,kCAAkC;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,iEAAiE;AACjE,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAC1C,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAE7C,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAW/D;AAED,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMlG;AAoCD,qCAAqC;AACrC,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,CAgB9F;AAED,qCAAqC;AACrC,wBAAsB,gBAAgB,CACpC,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,uBAA4B,EACrC,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,eAAe,CAAC,CAmB1B"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { ErrorCode, RtcError } from './errors.js';
|
|
2
|
+
/** 服务端每页上限(`maxCallLimit`)。超过它服务端也只回这么多,「到底」的判据就不成立了,所以本地先夹住。 */
|
|
3
|
+
export const MAX_CALL_HISTORY_LIMIT = 200;
|
|
4
|
+
export const DEFAULT_CALL_HISTORY_LIMIT = 20;
|
|
5
|
+
/** 信令地址推出 REST 根:`ws→http`、`wss→https`,去掉末尾的 `/v1/ws`。推不出返回 `null`。 */
|
|
6
|
+
export function restBaseUrl(signalingUrl) {
|
|
7
|
+
let url;
|
|
8
|
+
try {
|
|
9
|
+
url = new URL(signalingUrl);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const scheme = { 'ws:': 'http:', 'wss:': 'https:', 'http:': 'http:', 'https:': 'https:' }[url.protocol];
|
|
15
|
+
if (scheme === undefined)
|
|
16
|
+
return null;
|
|
17
|
+
const path = url.pathname.endsWith('/v1/ws') ? url.pathname.slice(0, -'/v1/ws'.length) : url.pathname;
|
|
18
|
+
return `${scheme}//${url.host}${path.replace(/\/$/, '')}`;
|
|
19
|
+
}
|
|
20
|
+
export function callHistoryUrl(signalingUrl, limit, cursor) {
|
|
21
|
+
const base = restBaseUrl(signalingUrl);
|
|
22
|
+
if (base === null)
|
|
23
|
+
return null;
|
|
24
|
+
const query = new URLSearchParams({ limit: String(limit) });
|
|
25
|
+
if (cursor !== undefined && cursor > 0)
|
|
26
|
+
query.set('cursor', String(cursor));
|
|
27
|
+
return `${base}/v1/calls?${query.toString()}`;
|
|
28
|
+
}
|
|
29
|
+
function str(o, key) {
|
|
30
|
+
const v = o[key];
|
|
31
|
+
return typeof v === 'string' ? v : '';
|
|
32
|
+
}
|
|
33
|
+
function num(o, key) {
|
|
34
|
+
const v = o[key];
|
|
35
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : 0;
|
|
36
|
+
}
|
|
37
|
+
function isObject(v) {
|
|
38
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
39
|
+
}
|
|
40
|
+
function toRecord(o) {
|
|
41
|
+
const members = Array.isArray(o['members']) ? o['members'].filter(isObject) : [];
|
|
42
|
+
return {
|
|
43
|
+
callId: str(o, 'call_id'),
|
|
44
|
+
roomId: str(o, 'room_id'),
|
|
45
|
+
caller: str(o, 'caller'),
|
|
46
|
+
mediaType: str(o, 'media_type'),
|
|
47
|
+
isGroup: o['is_group'] === true,
|
|
48
|
+
reason: str(o, 'reason'),
|
|
49
|
+
endedBy: str(o, 'ended_by'),
|
|
50
|
+
durationSec: num(o, 'duration_sec'),
|
|
51
|
+
startedAtMs: num(o, 'started_at_ms'),
|
|
52
|
+
connectedAtMs: num(o, 'connected_at_ms'),
|
|
53
|
+
endedAtMs: num(o, 'ended_at_ms'),
|
|
54
|
+
userData: str(o, 'user_data'),
|
|
55
|
+
chatGroupId: str(o, 'chat_group_id'),
|
|
56
|
+
members: members.map((m) => ({ uid: str(m, 'uid'), state: str(m, 'state') })),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** 状态码与应答体 → 一页记录;出错抛 `RtcError`。 */
|
|
60
|
+
export function parseCallHistory(status, body, limit) {
|
|
61
|
+
if (status === 401) {
|
|
62
|
+
throw new RtcError(ErrorCode.tokenInvalid, { cause: new Error('查通话记录被拒(401):票无效或已过期') });
|
|
63
|
+
}
|
|
64
|
+
if (status !== 200) {
|
|
65
|
+
throw new RtcError(ErrorCode.internal, { cause: new Error(`查通话记录失败:HTTP ${status}`) });
|
|
66
|
+
}
|
|
67
|
+
if (!isObject(body)) {
|
|
68
|
+
throw new RtcError(ErrorCode.internal, { cause: new Error('通话记录应答不是对象') });
|
|
69
|
+
}
|
|
70
|
+
const calls = Array.isArray(body['calls']) ? body['calls'].filter(isObject) : [];
|
|
71
|
+
const records = calls.map(toRecord);
|
|
72
|
+
const next = typeof body['next_cursor'] === 'number' ? body['next_cursor'] : null;
|
|
73
|
+
// 服务端只要这页有数据就给 next_cursor,没有「到底」标志:
|
|
74
|
+
// 不满一页就一定是最后一页,满页才交出游标(最坏多翻一页空的)。
|
|
75
|
+
return { records, nextCursor: records.length >= limit ? next : null };
|
|
76
|
+
}
|
|
77
|
+
/** 发请求并解析。`fetchImpl` 可注入,单测不碰网络。 */
|
|
78
|
+
export async function fetchCallHistory(signalingUrl, token, options = {}, fetchImpl = fetch) {
|
|
79
|
+
const limit = Math.min(Math.max(Math.trunc(options.limit ?? DEFAULT_CALL_HISTORY_LIMIT), 1), MAX_CALL_HISTORY_LIMIT);
|
|
80
|
+
const url = callHistoryUrl(signalingUrl, limit, options.cursor);
|
|
81
|
+
if (url === null) {
|
|
82
|
+
throw new RtcError(ErrorCode.badParams, { cause: new Error(`信令地址无法推出 REST 地址:${signalingUrl}`) });
|
|
83
|
+
}
|
|
84
|
+
let response;
|
|
85
|
+
try {
|
|
86
|
+
response = await fetchImpl(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
throw new RtcError(ErrorCode.networkUnreachable, { cause: err });
|
|
90
|
+
}
|
|
91
|
+
let body = null;
|
|
92
|
+
try {
|
|
93
|
+
body = await response.json();
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// 非 JSON:交给 parseCallHistory 按状态码报错。
|
|
97
|
+
}
|
|
98
|
+
return parseCallHistory(response.status, body, limit);
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=callHistory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callHistory.js","sourceRoot":"","sources":["../src/callHistory.ts"],"names":[],"mappings":"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"}
|
package/dist/callOptions.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { MediaType } from './signaling/enums.js';
|
|
1
2
|
/**
|
|
2
3
|
* CallOptions 是 {@link CallEngine.call} 的可选项(HOST_INTEGRATION_DESIGN §3.3)。
|
|
3
4
|
*
|
|
@@ -24,4 +25,17 @@ export interface CallOptions {
|
|
|
24
25
|
* (`''` 表示宿主没给这个可选项)。
|
|
25
26
|
*/
|
|
26
27
|
export declare function violatesCallOptionLimits(chatGroupId: string, userData: string): boolean;
|
|
28
|
+
/** CallRequest 是 `call()` 整形之后的样子:状态机要的 `args`,外加本地校验要看的两个字段。 */
|
|
29
|
+
export interface CallRequest {
|
|
30
|
+
readonly args: Record<string, unknown>;
|
|
31
|
+
readonly chatGroupId: string;
|
|
32
|
+
readonly userData: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* toCallRequest 把 `call()` 的参数整形成状态机的 `act` 参数(snake_case)。
|
|
36
|
+
*
|
|
37
|
+
* `options` 传布尔值等同旧的 `isGroup`。**没给的可选项省略不写**,不写成空串——
|
|
38
|
+
* 省略表达「没传」,见 callMachine.ts startCall 的同一条注释。
|
|
39
|
+
*/
|
|
40
|
+
export declare function toCallRequest(calleeIds: string[], mediaType: MediaType, options?: boolean | CallOptions): CallRequest;
|
|
27
41
|
//# sourceMappingURL=callOptions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"callOptions.d.ts","sourceRoot":"","sources":["../src/callOptions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"callOptions.d.ts","sourceRoot":"","sources":["../src/callOptions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAOD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAKvF;AAED,iEAAiE;AACjE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EAAE,EACnB,SAAS,EAAE,SAAS,EACpB,OAAO,CAAC,EAAE,OAAO,GAAG,WAAW,GAC9B,WAAW,CAWb"}
|
package/dist/callOptions.js
CHANGED
|
@@ -16,4 +16,25 @@ export function violatesCallOptionLimits(chatGroupId, userData) {
|
|
|
16
16
|
}
|
|
17
17
|
return byteLength(userData) > MAX_USER_DATA_BYTES;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* toCallRequest 把 `call()` 的参数整形成状态机的 `act` 参数(snake_case)。
|
|
21
|
+
*
|
|
22
|
+
* `options` 传布尔值等同旧的 `isGroup`。**没给的可选项省略不写**,不写成空串——
|
|
23
|
+
* 省略表达「没传」,见 callMachine.ts startCall 的同一条注释。
|
|
24
|
+
*/
|
|
25
|
+
export function toCallRequest(calleeIds, mediaType, options) {
|
|
26
|
+
const opts = typeof options === 'boolean' ? { isGroup: options } : (options ?? {});
|
|
27
|
+
const chatGroupId = opts.chatGroupId ?? '';
|
|
28
|
+
const userData = opts.userData ?? '';
|
|
29
|
+
const args = {
|
|
30
|
+
callee_ids: calleeIds, media_type: mediaType, is_group: opts.isGroup ?? false,
|
|
31
|
+
};
|
|
32
|
+
if (chatGroupId !== '')
|
|
33
|
+
args['chat_group_id'] = chatGroupId;
|
|
34
|
+
if (userData !== '')
|
|
35
|
+
args['user_data'] = userData;
|
|
36
|
+
if (opts.timeoutSec !== undefined)
|
|
37
|
+
args['timeout_sec'] = opts.timeoutSec;
|
|
38
|
+
return { args, chatGroupId, userData };
|
|
39
|
+
}
|
|
19
40
|
//# sourceMappingURL=callOptions.js.map
|
package/dist/callOptions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"callOptions.js","sourceRoot":"","sources":["../src/callOptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"callOptions.js","sourceRoot":"","sources":["../src/callOptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAsBxC,mCAAmC;AACnC,MAAM,uBAAuB,GAAG,EAAE,CAAC;AACnC,+BAA+B;AAC/B,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAEjC;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CAAC,WAAmB,EAAE,QAAgB;IAC5E,IAAI,WAAW,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,GAAG,uBAAuB,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC;QACxG,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,UAAU,CAAC,QAAQ,CAAC,GAAG,mBAAmB,CAAC;AACpD,CAAC;AASD;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAC3B,SAAmB,EACnB,SAAoB,EACpB,OAA+B;IAE/B,MAAM,IAAI,GAAgB,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IAChG,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;IAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;IACrC,MAAM,IAAI,GAA4B;QACpC,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,OAAO,IAAI,KAAK;KAC9E,CAAC;IACF,IAAI,WAAW,KAAK,EAAE;QAAE,IAAI,CAAC,eAAe,CAAC,GAAG,WAAW,CAAC;IAC5D,IAAI,QAAQ,KAAK,EAAE;QAAE,IAAI,CAAC,WAAW,CAAC,GAAG,QAAQ,CAAC;IAClD,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS;QAAE,IAAI,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC;IACzE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 调试密钥本地签票(设计稿 `im-rtc-server/docs/design/DEBUG_KEY_DESIGN.md` §4)。
|
|
3
|
+
*
|
|
4
|
+
* **仅联调用**:把调试密钥放进客户端,等于把签票权交给所有拿到包的人。
|
|
5
|
+
* 上线必须换成宿主后端 `POST /v1/tokens`。服务端只认 kid 以 `dbg-` 开头的调试密钥。
|
|
6
|
+
* 向量:`im-rtc-server/docs/conformance/debug_token.json`。
|
|
7
|
+
*/
|
|
8
|
+
/** DebugTokenParams 是本地签票的入参。 */
|
|
9
|
+
export interface DebugTokenParams {
|
|
10
|
+
readonly appId: string;
|
|
11
|
+
/** 调试密钥 id,必须以 `dbg-` 开头。 */
|
|
12
|
+
readonly keyId: string;
|
|
13
|
+
readonly secret: string;
|
|
14
|
+
readonly uid: string;
|
|
15
|
+
readonly deviceId?: string;
|
|
16
|
+
/** 有效期(秒)。缺省或 0 = 12h,钳到 [60, 2592000](30 天)。 */
|
|
17
|
+
readonly ttlSec?: number;
|
|
18
|
+
/** 注入当前时间(Unix 毫秒),测试用;缺省 `Date.now()`。 */
|
|
19
|
+
readonly nowMs?: number;
|
|
20
|
+
}
|
|
21
|
+
/** hmacSha256B64url 返回 base64url(HMAC-SHA256(secret, input)),无填充。 */
|
|
22
|
+
export declare function hmacSha256B64url(secret: string, input: string): Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* generateDebugToken 在本地用调试密钥签一张 HS256 JWT。**仅联调,上线换后端 `/v1/tokens`。**
|
|
25
|
+
*
|
|
26
|
+
* 入参不合法抛 `RtcError(1004)`。
|
|
27
|
+
*/
|
|
28
|
+
export declare function generateDebugToken(params: DebugTokenParams): Promise<string>;
|
|
29
|
+
//# sourceMappingURL=debugToken.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debugToken.d.ts","sourceRoot":"","sources":["../src/debugToken.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AAEH,iCAAiC;AACjC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6BAA6B;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,iDAAiD;IACjD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAiBD,qEAAqE;AACrE,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAcrF;AAmBD;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBlF"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { byteLength } from './bytes.js';
|
|
2
|
+
import { ErrorCode, RtcError } from './errors.js';
|
|
3
|
+
import { logger } from './logger.js';
|
|
4
|
+
const DEFAULT_TTL_SEC = 12 * 3600;
|
|
5
|
+
const MIN_TTL_SEC = 60;
|
|
6
|
+
const MAX_TTL_SEC = 30 * 24 * 3600;
|
|
7
|
+
const MAX_UID_BYTES = 64;
|
|
8
|
+
const ISSUER = 'im-rtc-server';
|
|
9
|
+
const encoder = new TextEncoder();
|
|
10
|
+
/** base64url 无填充。 */
|
|
11
|
+
function b64url(bytes) {
|
|
12
|
+
let bin = '';
|
|
13
|
+
for (const b of bytes)
|
|
14
|
+
bin += String.fromCharCode(b);
|
|
15
|
+
return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
16
|
+
}
|
|
17
|
+
/** hmacSha256B64url 返回 base64url(HMAC-SHA256(secret, input)),无填充。 */
|
|
18
|
+
export async function hmacSha256B64url(secret, input) {
|
|
19
|
+
const subtle = globalThis.crypto?.subtle;
|
|
20
|
+
if (subtle === undefined) {
|
|
21
|
+
// 非安全上下文(http 非 localhost)没有 crypto.subtle。
|
|
22
|
+
throw new RtcError(ErrorCode.internal, { cause: new Error('crypto.subtle 不可用(需 HTTPS 或 localhost)') });
|
|
23
|
+
}
|
|
24
|
+
const key = await subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
25
|
+
return b64url(new Uint8Array(await subtle.sign('HMAC', key, encoder.encode(input))));
|
|
26
|
+
}
|
|
27
|
+
function validate(p) {
|
|
28
|
+
const bad = (why) => {
|
|
29
|
+
throw new RtcError(ErrorCode.badParams, { forType: `generateDebugToken: ${why}` });
|
|
30
|
+
};
|
|
31
|
+
if (p.appId === '')
|
|
32
|
+
bad('appId 为空');
|
|
33
|
+
if (p.secret === '')
|
|
34
|
+
bad('secret 为空');
|
|
35
|
+
if (!p.keyId.startsWith('dbg-'))
|
|
36
|
+
bad('keyId 必须以 dbg- 开头(不要把生产密钥放进客户端)');
|
|
37
|
+
if (p.uid === '')
|
|
38
|
+
bad('uid 为空');
|
|
39
|
+
if (/\s/u.test(p.uid))
|
|
40
|
+
bad('uid 含空白');
|
|
41
|
+
if (byteLength(p.uid) > MAX_UID_BYTES)
|
|
42
|
+
bad(`uid 超过 ${MAX_UID_BYTES} 字节`);
|
|
43
|
+
}
|
|
44
|
+
function clampTtl(ttlSec) {
|
|
45
|
+
if (ttlSec === undefined || ttlSec === 0 || !Number.isFinite(ttlSec))
|
|
46
|
+
return DEFAULT_TTL_SEC;
|
|
47
|
+
return Math.min(MAX_TTL_SEC, Math.max(MIN_TTL_SEC, Math.floor(ttlSec)));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* generateDebugToken 在本地用调试密钥签一张 HS256 JWT。**仅联调,上线换后端 `/v1/tokens`。**
|
|
51
|
+
*
|
|
52
|
+
* 入参不合法抛 `RtcError(1004)`。
|
|
53
|
+
*/
|
|
54
|
+
export async function generateDebugToken(params) {
|
|
55
|
+
validate(params);
|
|
56
|
+
// 走 logger(默认 sink 即 console.warn;仓库禁止业务代码直接用 console)。
|
|
57
|
+
logger.warn('【仅联调】正在用调试密钥在客户端本地签票——上线必须换成后端 /v1/tokens,勿把密钥带进正式包');
|
|
58
|
+
const iat = Math.floor((params.nowMs ?? Date.now()) / 1000);
|
|
59
|
+
const claims = {
|
|
60
|
+
iss: ISSUER,
|
|
61
|
+
sub: params.uid,
|
|
62
|
+
aud: params.appId,
|
|
63
|
+
exp: iat + clampTtl(params.ttlSec),
|
|
64
|
+
iat,
|
|
65
|
+
scope: 'access',
|
|
66
|
+
};
|
|
67
|
+
if (params.deviceId !== undefined && params.deviceId !== '')
|
|
68
|
+
claims['did'] = params.deviceId;
|
|
69
|
+
const header = { alg: 'HS256', typ: 'JWT', kid: params.keyId };
|
|
70
|
+
const signingInput = `${b64url(encoder.encode(JSON.stringify(header)))}.${b64url(encoder.encode(JSON.stringify(claims)))}`;
|
|
71
|
+
return `${signingInput}.${await hmacSha256B64url(params.secret, signingInput)}`;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=debugToken.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debugToken.js","sourceRoot":"","sources":["../src/debugToken.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;AAwBrC,MAAM,eAAe,GAAG,EAAE,GAAG,IAAI,CAAC;AAClC,MAAM,WAAW,GAAG,EAAE,CAAC;AACvB,MAAM,WAAW,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AACnC,MAAM,aAAa,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,GAAG,eAAe,CAAC;AAE/B,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;AAElC,qBAAqB;AACrB,SAAS,MAAM,CAAC,KAAiB;IAC/B,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACrD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED,qEAAqE;AACrE,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAc,EAAE,KAAa;IAClE,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC;IACzC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,4CAA4C;QAC5C,MAAM,IAAI,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,KAAK,CAAC,wCAAwC,CAAC,EAAE,CAAC,CAAC;IACzG,CAAC;IACD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,SAAS,CAChC,KAAK,EACL,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EACtB,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,EACjC,KAAK,EACL,CAAC,MAAM,CAAC,CACT,CAAC;IACF,OAAO,MAAM,CAAC,IAAI,UAAU,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACvF,CAAC;AAED,SAAS,QAAQ,CAAC,CAAmB;IACnC,MAAM,GAAG,GAAG,CAAC,GAAW,EAAS,EAAE;QACjC,MAAM,IAAI,QAAQ,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,uBAAuB,GAAG,EAAE,EAAE,CAAC,CAAC;IACrF,CAAC,CAAC;IACF,IAAI,CAAC,CAAC,KAAK,KAAK,EAAE;QAAE,GAAG,CAAC,UAAU,CAAC,CAAC;IACpC,IAAI,CAAC,CAAC,MAAM,KAAK,EAAE;QAAE,GAAG,CAAC,WAAW,CAAC,CAAC;IACtC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,GAAG,CAAC,iCAAiC,CAAC,CAAC;IACxE,IAAI,CAAC,CAAC,GAAG,KAAK,EAAE;QAAE,GAAG,CAAC,QAAQ,CAAC,CAAC;IAChC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;QAAE,GAAG,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,aAAa;QAAE,GAAG,CAAC,UAAU,aAAa,KAAK,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,QAAQ,CAAC,MAA0B;IAC1C,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,eAAe,CAAC;IAC7F,OAAO,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAwB;IAC/D,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjB,wDAAwD;IACxD,MAAM,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC;IAEnE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IAC5D,MAAM,MAAM,GAAoC;QAC9C,GAAG,EAAE,MAAM;QACX,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,GAAG,EAAE,MAAM,CAAC,KAAK;QACjB,GAAG,EAAE,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;QAClC,GAAG;QACH,KAAK,EAAE,QAAQ;KAChB,CAAC;IACF,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,KAAK,EAAE;QAAE,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC;IAE7F,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;IAC/D,MAAM,YAAY,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3H,OAAO,GAAG,YAAY,IAAI,MAAM,gBAAgB,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC;AAClF,CAAC"}
|