wenay-common2 1.0.66 → 1.0.68
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/doc/ROADMAP.md +110 -94
- package/doc/changes/1.0.67.md +10 -0
- package/doc/changes/1.0.68.md +8 -0
- package/doc/wenay-common2-rare.md +56 -5
- package/lib/Common/events/replay-channel.d.ts +9 -0
- package/lib/Common/events/replay-channel.js +129 -0
- package/lib/Common/events/replay-index.d.ts +3 -0
- package/lib/Common/events/replay-index.js +3 -0
- package/lib/Common/events/route-coordinator.d.ts +274 -0
- package/lib/Common/events/route-coordinator.js +316 -0
- package/lib/Common/events/route-signal-webrtc.d.ts +83 -0
- package/lib/Common/events/route-signal-webrtc.js +245 -0
- package/package.json +1 -1
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSignalHub = createSignalHub;
|
|
4
|
+
exports.channelFromDataChannel = channelFromDataChannel;
|
|
5
|
+
exports.createWebRtcConnector = createWebRtcConnector;
|
|
6
|
+
exports.acceptWebRtcDirect = acceptWebRtcDirect;
|
|
7
|
+
const Listen_1 = require("./Listen");
|
|
8
|
+
const replay_channel_1 = require("./replay-channel");
|
|
9
|
+
function createSignalHub(deps = {}) {
|
|
10
|
+
const { authorize } = deps;
|
|
11
|
+
const ports = new Map();
|
|
12
|
+
function register(account) {
|
|
13
|
+
const [emit, signals] = (0, Listen_1.listen)();
|
|
14
|
+
ports.set(account, emit);
|
|
15
|
+
async function send(env) {
|
|
16
|
+
if (env == null || env.from != account)
|
|
17
|
+
return false;
|
|
18
|
+
if (authorize && !(await authorize(env)))
|
|
19
|
+
return false;
|
|
20
|
+
const target = ports.get(env.to);
|
|
21
|
+
if (!target)
|
|
22
|
+
return false;
|
|
23
|
+
target(env);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
function close() {
|
|
27
|
+
if (ports.get(account) == emit)
|
|
28
|
+
ports.delete(account);
|
|
29
|
+
signals.close();
|
|
30
|
+
}
|
|
31
|
+
return { account, send, signals, close };
|
|
32
|
+
}
|
|
33
|
+
function revoke(pair, accounts, reason) {
|
|
34
|
+
for (const account of accounts) {
|
|
35
|
+
ports.get(account)?.({ type: 'revoke', pair, from: '', to: account, reason });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
register,
|
|
40
|
+
revoke,
|
|
41
|
+
accounts: () => Array.from(ports.keys()),
|
|
42
|
+
close() {
|
|
43
|
+
ports.clear();
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function channelFromDataChannel(dc) {
|
|
48
|
+
const msgCbs = new Set();
|
|
49
|
+
const closeCbs = new Set();
|
|
50
|
+
let closed = false;
|
|
51
|
+
function fireClose() {
|
|
52
|
+
if (closed)
|
|
53
|
+
return;
|
|
54
|
+
closed = true;
|
|
55
|
+
for (const cb of Array.from(closeCbs))
|
|
56
|
+
cb();
|
|
57
|
+
}
|
|
58
|
+
dc.onmessage = function onDcMessage(ev) {
|
|
59
|
+
const data = String(ev.data);
|
|
60
|
+
for (const cb of Array.from(msgCbs))
|
|
61
|
+
cb(data);
|
|
62
|
+
};
|
|
63
|
+
dc.onclose = fireClose;
|
|
64
|
+
dc.onerror = fireClose;
|
|
65
|
+
return {
|
|
66
|
+
send: data => dc.send(data),
|
|
67
|
+
onMessage: cb => { msgCbs.add(cb); return () => msgCbs.delete(cb); },
|
|
68
|
+
onClose: cb => { closeCbs.add(cb); return () => closeCbs.delete(cb); },
|
|
69
|
+
close: () => { dc.close(); fireClose(); },
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function createWebRtcConnector(deps) {
|
|
73
|
+
const { port, rtc, self, peer, pair, session, label = 'direct', openTimeoutMs = 10_000 } = deps;
|
|
74
|
+
let state = 'idle';
|
|
75
|
+
let pc = null;
|
|
76
|
+
let channel = null;
|
|
77
|
+
let offSignals = null;
|
|
78
|
+
let abortOpen = null;
|
|
79
|
+
const [emitFail, failListen] = (0, Listen_1.listen)();
|
|
80
|
+
function teardown(next) {
|
|
81
|
+
state = next;
|
|
82
|
+
if (typeof offSignals == 'function')
|
|
83
|
+
offSignals();
|
|
84
|
+
else
|
|
85
|
+
offSignals?.off?.();
|
|
86
|
+
offSignals = null;
|
|
87
|
+
channel?.close?.();
|
|
88
|
+
channel = null;
|
|
89
|
+
pc?.close();
|
|
90
|
+
pc = null;
|
|
91
|
+
}
|
|
92
|
+
function fail(reason) {
|
|
93
|
+
if (state == 'closed' || state == 'failed')
|
|
94
|
+
return;
|
|
95
|
+
const abort = abortOpen;
|
|
96
|
+
abortOpen = null;
|
|
97
|
+
teardown('failed');
|
|
98
|
+
abort?.(reason instanceof Error ? reason : new Error(String(reason)));
|
|
99
|
+
emitFail(reason);
|
|
100
|
+
}
|
|
101
|
+
async function open() {
|
|
102
|
+
state = 'opening';
|
|
103
|
+
const me = rtc();
|
|
104
|
+
pc = me;
|
|
105
|
+
const dc = me.createDataChannel('replay');
|
|
106
|
+
let openTimer = null;
|
|
107
|
+
const opened = new Promise((resolve, reject) => {
|
|
108
|
+
abortOpen = reject;
|
|
109
|
+
dc.onopen = () => resolve();
|
|
110
|
+
openTimer = setTimeout(function webRtcOpenTimeout() {
|
|
111
|
+
reject(new Error('webrtc direct open timeout: ' + pair));
|
|
112
|
+
}, openTimeoutMs);
|
|
113
|
+
});
|
|
114
|
+
opened.catch(() => { });
|
|
115
|
+
offSignals = port.signals.on(function onSignal(env) {
|
|
116
|
+
if (env == null || env.pair != pair || env.to != self || pc != me)
|
|
117
|
+
return;
|
|
118
|
+
if (env.type == 'answer' && env.sdp != null) {
|
|
119
|
+
void Promise.resolve(me.setRemoteDescription({ type: 'answer', sdp: env.sdp }))
|
|
120
|
+
.catch(fail);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (env.type == 'ice' && env.candidate != null) {
|
|
124
|
+
void Promise.resolve(me.addIceCandidate(env.candidate)).catch(fail);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (env.type == 'revoke' || env.type == 'close') {
|
|
128
|
+
fail(new Error('direct route ' + env.type + (env.reason ? ': ' + env.reason : '')));
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
me.onicecandidate = function onIce(ev) {
|
|
132
|
+
if (ev?.candidate != null) {
|
|
133
|
+
void port.send({ type: 'ice', pair, from: self, to: peer, candidate: ev.candidate });
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
try {
|
|
137
|
+
const offer = await me.createOffer();
|
|
138
|
+
await me.setLocalDescription(offer);
|
|
139
|
+
const accepted = await port.send({ type: 'offer', pair, from: self, to: peer, sdp: offer.sdp, session });
|
|
140
|
+
if (accepted == false)
|
|
141
|
+
throw new Error('signaling rejected offer (endpoint not exposed): ' + pair);
|
|
142
|
+
await opened;
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
teardown('failed');
|
|
146
|
+
throw e;
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
clearTimeout(openTimer);
|
|
150
|
+
abortOpen = null;
|
|
151
|
+
}
|
|
152
|
+
state = 'open';
|
|
153
|
+
channel = channelFromDataChannel(dc);
|
|
154
|
+
channel.onClose?.(function onDirectChannelDied() {
|
|
155
|
+
if (state == 'open')
|
|
156
|
+
fail(new Error('direct channel closed: ' + pair));
|
|
157
|
+
});
|
|
158
|
+
return (0, replay_channel_1.channelReplayRemote)(channel);
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
info: { label, kind: 'direct', binary: false, ordered: true, reliable: true },
|
|
162
|
+
open,
|
|
163
|
+
close() {
|
|
164
|
+
if (state == 'closed')
|
|
165
|
+
return;
|
|
166
|
+
void port.send({ type: 'close', pair, from: self, to: peer });
|
|
167
|
+
teardown('closed');
|
|
168
|
+
},
|
|
169
|
+
state: () => state,
|
|
170
|
+
onFail: { on: cb => failListen.on(cb) },
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function acceptWebRtcDirect(deps) {
|
|
174
|
+
const { port, rtc, self, serve, accept } = deps;
|
|
175
|
+
const sessions = new Map();
|
|
176
|
+
let closed = false;
|
|
177
|
+
function dropSession(key) {
|
|
178
|
+
const s = sessions.get(key);
|
|
179
|
+
if (!s)
|
|
180
|
+
return;
|
|
181
|
+
sessions.delete(key);
|
|
182
|
+
s.stop?.();
|
|
183
|
+
s.pc.close();
|
|
184
|
+
}
|
|
185
|
+
async function onOffer(env) {
|
|
186
|
+
const key = env.pair + '|' + env.from;
|
|
187
|
+
if (accept && !(await accept(env))) {
|
|
188
|
+
void port.send({ type: 'revoke', pair: env.pair, from: self, to: env.from, reason: 'offer rejected' });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const source = await serve(env);
|
|
192
|
+
if (!source) {
|
|
193
|
+
void port.send({ type: 'revoke', pair: env.pair, from: self, to: env.from, reason: 'nothing to serve' });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
dropSession(key);
|
|
197
|
+
const pc = rtc();
|
|
198
|
+
const session = { pc, stop: null };
|
|
199
|
+
sessions.set(key, session);
|
|
200
|
+
pc.ondatachannel = function onIncomingChannel(ev) {
|
|
201
|
+
session.stop = (0, replay_channel_1.serveReplayChannel)(source, channelFromDataChannel(ev.channel));
|
|
202
|
+
};
|
|
203
|
+
pc.onicecandidate = function onIce(ev) {
|
|
204
|
+
if (ev?.candidate != null) {
|
|
205
|
+
void port.send({ type: 'ice', pair: env.pair, from: self, to: env.from, candidate: ev.candidate });
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
try {
|
|
209
|
+
await pc.setRemoteDescription({ type: 'offer', sdp: env.sdp });
|
|
210
|
+
const answer = await pc.createAnswer();
|
|
211
|
+
await pc.setLocalDescription(answer);
|
|
212
|
+
void port.send({ type: 'answer', pair: env.pair, from: self, to: env.from, sdp: answer.sdp });
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
dropSession(key);
|
|
216
|
+
void port.send({ type: 'revoke', pair: env.pair, from: self, to: env.from, reason: 'negotiation failed' });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const offSignals = port.signals.on(function onAcceptSignal(env) {
|
|
220
|
+
if (closed || env == null || env.to != self)
|
|
221
|
+
return;
|
|
222
|
+
if (env.type == 'offer') {
|
|
223
|
+
void onOffer(env);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const key = env.pair + '|' + env.from;
|
|
227
|
+
if (env.type == 'ice' && env.candidate != null) {
|
|
228
|
+
void Promise.resolve(sessions.get(key)?.pc.addIceCandidate(env.candidate)).catch(() => dropSession(key));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (env.type == 'close' || env.type == 'revoke')
|
|
232
|
+
dropSession(key);
|
|
233
|
+
});
|
|
234
|
+
return function closeAccept() {
|
|
235
|
+
if (closed)
|
|
236
|
+
return;
|
|
237
|
+
closed = true;
|
|
238
|
+
if (typeof offSignals == 'function')
|
|
239
|
+
offSignals();
|
|
240
|
+
else
|
|
241
|
+
offSignals?.off?.();
|
|
242
|
+
for (const key of Array.from(sessions.keys()))
|
|
243
|
+
dropSession(key);
|
|
244
|
+
};
|
|
245
|
+
}
|