wenay-common2 1.0.65 → 1.0.67

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.
@@ -0,0 +1,316 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createRouteCoordinator = createRouteCoordinator;
4
+ const Listen_1 = require("./Listen");
5
+ const replay_route_1 = require("./replay-route");
6
+ function unsubscribeHandle(handle) {
7
+ if (typeof handle == 'function') {
8
+ handle();
9
+ return;
10
+ }
11
+ if (typeof handle?.off == 'function')
12
+ handle.off();
13
+ else if (typeof handle?.unsubscribe == 'function')
14
+ handle.unsubscribe();
15
+ }
16
+ function lazyRemote(connector) {
17
+ let opened = null;
18
+ const get = () => opened ??= Promise.resolve(connector.open());
19
+ function lazyLine(pick) {
20
+ return {
21
+ on(cb) {
22
+ let off = null;
23
+ let dead = false;
24
+ get().then(function attachLazyLine(r) { if (!dead)
25
+ off = pick(r).on(cb); }, function swallowOpenError() { });
26
+ return function offLazyLine() { dead = true; unsubscribeHandle(off); };
27
+ },
28
+ };
29
+ }
30
+ return {
31
+ line: lazyLine(r => r.line),
32
+ frameLine: lazyLine(r => r.frameLine ?? r.line),
33
+ since: async (seq) => (await get()).since(seq),
34
+ keyframe: async () => (await get()).keyframe(),
35
+ frame: async (seq, hint) => {
36
+ const r = await get();
37
+ return r.frame ? r.frame(seq, hint) : null;
38
+ },
39
+ };
40
+ }
41
+ function createRouteCoordinator(deps) {
42
+ const { policy = {}, connect, shadow, catchUpTimeoutMs } = deps;
43
+ const links = new Map();
44
+ const [emitRoute, routeListen] = (0, Listen_1.listen)();
45
+ async function allowed(hook, ctx, absent = true) {
46
+ return hook ? !!(await hook(ctx)) : absent;
47
+ }
48
+ function pairKey(a, b) {
49
+ return a <= b ? a + '|' + b : b + '|' + a;
50
+ }
51
+ function createLink(a, b) {
52
+ const ref = { a, b, key: pairKey(a, b) };
53
+ let state = 'relay';
54
+ let lastReason;
55
+ let relayConn = null;
56
+ let relayRemote = null;
57
+ let directConn = null;
58
+ let directRemote = null;
59
+ let shadowSub = null;
60
+ const subs = new Set();
61
+ let opChain = Promise.resolve();
62
+ function chained(run) {
63
+ const p = opChain.then(run, run);
64
+ opChain = p.catch(() => { });
65
+ return p;
66
+ }
67
+ function setState(to, reason) {
68
+ if (state == 'closed')
69
+ return;
70
+ const from = state;
71
+ state = to;
72
+ lastReason = reason;
73
+ emitRoute({ ...ref, from, to, reason });
74
+ }
75
+ function ctx(reason) {
76
+ return { ...ref, state, reason };
77
+ }
78
+ function ensureRelay() {
79
+ if (!relayConn || relayConn.state() == 'closed' || relayConn.state() == 'failed') {
80
+ relayConn = connect(ref, 'relay');
81
+ relayRemote = lazyRemote(relayConn);
82
+ watchFail(relayConn, 'relay');
83
+ }
84
+ return relayRemote;
85
+ }
86
+ function watchFail(conn, kind) {
87
+ conn.onFail?.on(function onConnectorFail(reason) {
88
+ if (kind == 'relay') {
89
+ if (conn == relayConn)
90
+ emitRoute({ ...ref, from: state, to: state, reason });
91
+ return;
92
+ }
93
+ if (conn == directConn && (state == 'direct' || state == 'direct+shadowRelay')) {
94
+ demoteToRelay('fallback', reason).catch(() => { });
95
+ }
96
+ });
97
+ }
98
+ function currentRemote() {
99
+ const direct = (state == 'direct' || state == 'direct+shadowRelay') && directRemote;
100
+ return direct || ensureRelay();
101
+ }
102
+ function currentLabel() {
103
+ const direct = (state == 'direct' || state == 'direct+shadowRelay') && directConn;
104
+ return direct ? directConn.info.label : (relayConn?.info.label ?? 'relay');
105
+ }
106
+ async function switchSubs(remote, label, timeoutMs) {
107
+ const jobs = Array.from(subs, function switchOne(sub) { return sub.switch(remote, { label }); });
108
+ if (timeoutMs == null)
109
+ return Promise.all(jobs);
110
+ let timer;
111
+ const timeout = new Promise((_, reject) => {
112
+ timer = setTimeout(function catchUpTimeout() {
113
+ reject(new Error('route catch-up timeout: ' + label));
114
+ }, timeoutMs);
115
+ });
116
+ try {
117
+ await Promise.race([Promise.all(jobs), timeout]);
118
+ }
119
+ finally {
120
+ clearTimeout(timer);
121
+ }
122
+ }
123
+ function resyncStraySubs() {
124
+ const remote = currentRemote();
125
+ const label = currentLabel();
126
+ for (const sub of subs) {
127
+ if (sub.label() != label)
128
+ sub.switch(remote, { label }).catch(() => { });
129
+ }
130
+ }
131
+ function attachShadow() {
132
+ if (!shadow || shadowSub)
133
+ return;
134
+ const since = subs.size ? Math.min(...Array.from(subs, sub => sub.seq())) : -1;
135
+ shadowSub = (0, replay_route_1.replayRouteSubscribe)(ensureRelay(), function shadowTap(...ev) { shadow(ref, ...ev); }, { label: 'shadowRelay', since });
136
+ }
137
+ function dropShadow() {
138
+ shadowSub?.();
139
+ shadowSub = null;
140
+ }
141
+ function closeDirect() {
142
+ dropShadow();
143
+ directConn?.close();
144
+ directConn = null;
145
+ directRemote = null;
146
+ }
147
+ async function demoteToRelay(finalState, reason) {
148
+ return chained(async function demoteOp() {
149
+ if (state == 'blocked' || state == 'closed')
150
+ return { ok: false, state, reason: state };
151
+ if (state != 'direct' && state != 'direct+shadowRelay') {
152
+ if (finalState == 'fallback')
153
+ setState('fallback', reason);
154
+ return { ok: true, state };
155
+ }
156
+ if (finalState == 'relay' && !(await allowed(policy.canReinterpose, ctx(reason)))) {
157
+ return { ok: false, state, reason: 'policy: canReinterpose' };
158
+ }
159
+ setState('relay:reinterposing', reason);
160
+ const remote = ensureRelay();
161
+ try {
162
+ await switchSubs(remote, relayConn.info.label);
163
+ closeDirect();
164
+ setState(finalState, reason);
165
+ resyncStraySubs();
166
+ return { ok: true, state };
167
+ }
168
+ catch (e) {
169
+ setState(shadowSub ? 'direct+shadowRelay' : 'direct', e);
170
+ return { ok: false, state, reason: e };
171
+ }
172
+ });
173
+ }
174
+ async function promoteDirect(opts = {}) {
175
+ return chained(async function promoteOp() {
176
+ const { timeoutMs = catchUpTimeoutMs, reason } = opts;
177
+ if (state == 'blocked' || state == 'closed')
178
+ return { ok: false, state, reason: state };
179
+ if (state == 'direct' || state == 'direct+shadowRelay')
180
+ return { ok: true, state };
181
+ if (await allowed(policy.mustRelay, ctx(reason), false)) {
182
+ return { ok: false, state, reason: 'policy: mustRelay' };
183
+ }
184
+ if (!(await allowed(policy.canDirect, ctx(reason)))) {
185
+ return { ok: false, state, reason: 'policy: canDirect' };
186
+ }
187
+ if (!(await allowed(policy.canExposeEndpoint, ctx(reason)))) {
188
+ return { ok: false, state, reason: 'policy: canExposeEndpoint' };
189
+ }
190
+ const wantShadow = await allowed(policy.mustShadowRelay, ctx(reason), false);
191
+ setState('direct:connecting', reason);
192
+ const conn = connect(ref, 'direct');
193
+ const remote = lazyRemote(conn);
194
+ try {
195
+ await switchSubs(remote, conn.info.label, timeoutMs);
196
+ directConn = conn;
197
+ directRemote = remote;
198
+ watchFail(conn, 'direct');
199
+ if (wantShadow) {
200
+ setState('direct+shadowRelay', reason);
201
+ attachShadow();
202
+ }
203
+ else {
204
+ relayConn?.close();
205
+ relayConn = null;
206
+ relayRemote = null;
207
+ setState('direct', reason);
208
+ }
209
+ resyncStraySubs();
210
+ return { ok: true, state };
211
+ }
212
+ catch (e) {
213
+ conn.close();
214
+ try {
215
+ await switchSubs(ensureRelay(), relayConn.info.label);
216
+ }
217
+ catch { }
218
+ setState('fallback', e);
219
+ return { ok: false, state, reason: e };
220
+ }
221
+ });
222
+ }
223
+ function block(reason) {
224
+ return chained(async function blockOp() {
225
+ if (state == 'closed')
226
+ return { ok: false, state, reason: state };
227
+ closeDirect();
228
+ relayConn?.close();
229
+ relayConn = null;
230
+ relayRemote = null;
231
+ for (const sub of Array.from(subs))
232
+ sub();
233
+ subs.clear();
234
+ setState('blocked', reason);
235
+ return { ok: true, state };
236
+ });
237
+ }
238
+ function subscribe(cb, opts = {}) {
239
+ if (state == 'blocked' || state == 'closed') {
240
+ throw new Error('route coordinator: pair ' + ref.key + ' is ' + state);
241
+ }
242
+ const sub = (0, replay_route_1.replayRouteSubscribe)(currentRemote(), cb, { ...opts, label: currentLabel() });
243
+ subs.add(sub);
244
+ function off() {
245
+ subs.delete(sub);
246
+ sub();
247
+ }
248
+ return Object.assign(off, {
249
+ ready: sub.ready,
250
+ seq: sub.seq,
251
+ label: sub.label,
252
+ active: sub.active,
253
+ });
254
+ }
255
+ function close() {
256
+ if (state == 'closed')
257
+ return;
258
+ closeDirect();
259
+ relayConn?.close();
260
+ relayConn = null;
261
+ relayRemote = null;
262
+ for (const sub of Array.from(subs))
263
+ sub();
264
+ subs.clear();
265
+ links.delete(ref.key);
266
+ setState('closed');
267
+ }
268
+ const link = {
269
+ ref,
270
+ state: () => state,
271
+ reason: () => lastReason,
272
+ label: currentLabel,
273
+ metrics: () => ({
274
+ relay: relayConn ? { state: relayConn.state(), ...relayConn.metrics?.() } : null,
275
+ direct: directConn ? { state: directConn.state(), ...directConn.metrics?.() } : null,
276
+ }),
277
+ subscribe,
278
+ promoteDirect,
279
+ reinterposeRelay: (reason) => demoteToRelay('relay', reason),
280
+ fallback: (reason) => demoteToRelay('fallback', reason),
281
+ block,
282
+ close,
283
+ };
284
+ return link;
285
+ }
286
+ function resolve(pairOrKey) {
287
+ const key = typeof pairOrKey == 'string' ? pairOrKey
288
+ : 'ref' in pairOrKey ? pairOrKey.ref.key : pairOrKey.key;
289
+ const link = links.get(key);
290
+ if (!link)
291
+ throw new Error('route coordinator: unknown pair ' + key);
292
+ return link;
293
+ }
294
+ return {
295
+ pair(a, b) {
296
+ const key = pairKey(a, b);
297
+ let link = links.get(key);
298
+ if (!link || link.state() == 'closed') {
299
+ link = createLink(a, b);
300
+ links.set(key, link);
301
+ }
302
+ return link;
303
+ },
304
+ state: (pairOrKey) => resolve(pairOrKey).state(),
305
+ promoteDirect: (pairOrKey, opts) => resolve(pairOrKey).promoteDirect(opts),
306
+ reinterposeRelay: (pairOrKey, reason) => resolve(pairOrKey).reinterposeRelay(reason),
307
+ fallback: (pairOrKey, reason) => resolve(pairOrKey).fallback(reason),
308
+ block: (pairOrKey, reason) => resolve(pairOrKey).block(reason),
309
+ onRoute: (cb) => routeListen.on(cb),
310
+ pairs: () => Array.from(links.values()),
311
+ close() {
312
+ for (const link of Array.from(links.values()))
313
+ link.close();
314
+ },
315
+ };
316
+ }
@@ -0,0 +1 @@
1
+ export * from './media-source';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./media-source"), exports);
@@ -0,0 +1,94 @@
1
+ import { Listener, ListenApi } from '../events/Listen';
2
+ import { ListenReplayApi, ReplayListenUseOptions } from '../events/replay-listen';
3
+ export type MediaSourceKind = 'audio' | 'video';
4
+ export type MediaSourceState = 'idle' | 'requesting' | 'live' | 'denied' | 'no-device' | 'error';
5
+ export type MediaFrameKind = 'audio-pcm' | 'audio-record' | 'video-frame';
6
+ export type MediaFrameCodec = 'pcm16' | 'float32' | 'jpeg' | 'png' | 'webp' | 'webm-opus';
7
+ export type MediaTransportMode = 'socket' | 'webrtc';
8
+ export type MediaSourceDevice = {
9
+ deviceId: string;
10
+ label: string;
11
+ kind: string;
12
+ };
13
+ export type MediaReplayOpts = true | ReplayListenUseOptions<[Uint8Array]>;
14
+ export type AudioSourceOpts = {
15
+ sourceId?: string;
16
+ deviceId?: string;
17
+ mode?: 'pcm' | 'record';
18
+ format?: 'int16' | 'float32';
19
+ channels?: number;
20
+ sampleRate?: number;
21
+ worklet?: boolean;
22
+ bufferSize?: number;
23
+ recordMimeType?: string;
24
+ recordTimesliceMs?: number;
25
+ transport?: MediaTransportMode;
26
+ replay?: MediaReplayOpts;
27
+ };
28
+ export type VideoSourceOpts = {
29
+ sourceId?: string;
30
+ deviceId?: string;
31
+ fps?: number;
32
+ width?: number;
33
+ height?: number;
34
+ codec?: 'jpeg' | 'png' | 'webp';
35
+ quality?: number;
36
+ video?: any;
37
+ canvas?: any;
38
+ worker?: false | {
39
+ transferable?: true;
40
+ };
41
+ transport?: MediaTransportMode;
42
+ replay?: MediaReplayOpts;
43
+ };
44
+ export type MediaStats = {
45
+ sourceId: string;
46
+ kind: MediaSourceKind;
47
+ state: MediaSourceState;
48
+ seq: number;
49
+ frames: number;
50
+ bytes: number;
51
+ dropped: number;
52
+ startedAt: number;
53
+ lastAt: number;
54
+ fps: number;
55
+ rms?: number;
56
+ error?: string;
57
+ };
58
+ export type MediaSourceControl = {
59
+ start(): Promise<MediaSourceState>;
60
+ stop(): void;
61
+ getStats(): MediaStats;
62
+ setDevice(id: string): Promise<MediaSourceState>;
63
+ listDevices(): Promise<MediaSourceDevice[]>;
64
+ readonly state: MediaSourceState;
65
+ readonly sourceId: string;
66
+ readonly kind: MediaSourceKind;
67
+ readonly transport: MediaTransportMode;
68
+ };
69
+ export type MediaListen = ListenApi<[Uint8Array]>;
70
+ export type MediaReplayListen = ListenReplayApi<[Uint8Array]>;
71
+ export type MediaAnyListen = MediaListen | MediaReplayListen;
72
+ export type MediaEmit = Listener<[Uint8Array]>;
73
+ export type MediaSource = readonly [MediaEmit, MediaAnyListen] & MediaSourceControl;
74
+ export type MediaFrameMeta = {
75
+ kind: MediaFrameKind;
76
+ codec: MediaFrameCodec;
77
+ seq: number;
78
+ tMono: number;
79
+ sampleRate?: number;
80
+ channels?: number;
81
+ nSamples?: number;
82
+ width?: number;
83
+ height?: number;
84
+ };
85
+ export type DecodedMediaFrame = MediaFrameMeta & {
86
+ payload: Uint8Array;
87
+ };
88
+ export declare const MEDIA_FRAME_MAGIC = 1464028466;
89
+ export declare const MEDIA_FRAME_VERSION = 1;
90
+ export declare const MEDIA_FRAME_HEADER_BYTES = 40;
91
+ export declare function encodeMediaFrame(meta: MediaFrameMeta, payload: ArrayBuffer | ArrayBufferView): Uint8Array<ArrayBuffer>;
92
+ export declare function decodeMediaFrame(frameLike: ArrayBuffer | ArrayBufferView): DecodedMediaFrame;
93
+ export declare function createAudioSource(opts?: AudioSourceOpts): MediaSource;
94
+ export declare function createVideoSource(opts?: VideoSourceOpts): MediaSource;