wenay-common2 1.0.74 → 1.0.76
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 +7 -6
- package/doc/changes/1.0.75.md +10 -0
- package/doc/changes/1.0.76.md +4 -0
- package/doc/wenay-common2-rare.md +38 -16
- package/doc/wenay-common2.md +438 -427
- package/lib/Common/events/replay-channel.js +55 -6
- package/lib/Common/events/replay-route.js +5 -2
- package/lib/Common/events/replay-wire.js +137 -47
- package/lib/Common/events/transport-lifecycle.d.ts +23 -0
- package/lib/Common/events/transport-lifecycle.js +94 -0
- package/lib/Common/rcp/rpc-client.js +346 -111
- package/lib/Common/rcp/rpc-clientHub.d.ts +2 -0
- package/lib/Common/rcp/rpc-clientHub.js +120 -25
- package/lib/Common/rcp/rpc-server.js +33 -9
- package/observe/store-manager.test.ts +43 -1
- package/oracle/realsocket/lifecycle.spec.ts +3 -3
- package/oracle/realsocket/replay-reconnect-auth.spec.ts +111 -0
- package/oracle/realsocket/replay-reconnect.spec.ts +592 -0
- package/package.json +1 -1
- package/replay/route-webrtc.test.ts +15 -2
- package/rpc.md +19 -4
- package/doc/changes/1.0.65.md +0 -8
- package/doc/changes/1.0.66.md +0 -8
|
@@ -2,6 +2,55 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.serveReplayChannel = serveReplayChannel;
|
|
4
4
|
exports.channelReplayRemote = channelReplayRemote;
|
|
5
|
+
const BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
6
|
+
const REPLAY_BYTES = '__wenayReplayBytes';
|
|
7
|
+
function bytesToBase64(bytes) {
|
|
8
|
+
let out = '';
|
|
9
|
+
for (let i = 0; i < bytes.length; i += 3) {
|
|
10
|
+
const a = bytes[i];
|
|
11
|
+
const b = bytes[i + 1];
|
|
12
|
+
const c = bytes[i + 2];
|
|
13
|
+
out += BASE64[a >> 2];
|
|
14
|
+
out += BASE64[((a & 3) << 4) | ((b ?? 0) >> 4)];
|
|
15
|
+
out += b == null ? '=' : BASE64[((b & 15) << 2) | ((c ?? 0) >> 6)];
|
|
16
|
+
out += c == null ? '=' : BASE64[c & 63];
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
function base64ToBytes(text) {
|
|
21
|
+
const clean = text.replace(/=+$/, '');
|
|
22
|
+
const out = new Uint8Array(Math.floor(clean.length * 3 / 4));
|
|
23
|
+
let bits = 0;
|
|
24
|
+
let nBits = 0;
|
|
25
|
+
let at = 0;
|
|
26
|
+
for (const char of clean) {
|
|
27
|
+
const n = BASE64.indexOf(char);
|
|
28
|
+
if (n < 0)
|
|
29
|
+
throw new Error('replay channel: invalid binary payload');
|
|
30
|
+
bits = (bits << 6) | n;
|
|
31
|
+
nBits += 6;
|
|
32
|
+
if (nBits < 8)
|
|
33
|
+
continue;
|
|
34
|
+
nBits -= 8;
|
|
35
|
+
out[at++] = (bits >> nBits) & 255;
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
function stringifyMessage(value) {
|
|
40
|
+
return JSON.stringify(value, function encodeReplayBytes(_key, item) {
|
|
41
|
+
if (item instanceof Uint8Array)
|
|
42
|
+
return { [REPLAY_BYTES]: bytesToBase64(item) };
|
|
43
|
+
return item;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function parseMessage(raw) {
|
|
47
|
+
return JSON.parse(raw, function decodeReplayBytes(_key, item) {
|
|
48
|
+
if (item != null && typeof item == 'object' && Object.keys(item).length == 1 && typeof item[REPLAY_BYTES] == 'string') {
|
|
49
|
+
return base64ToBytes(item[REPLAY_BYTES]);
|
|
50
|
+
}
|
|
51
|
+
return item;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
5
54
|
function unsubscribeHandle(handle) {
|
|
6
55
|
if (typeof handle == 'function') {
|
|
7
56
|
handle();
|
|
@@ -22,11 +71,11 @@ function serveReplayChannel(source, channel) {
|
|
|
22
71
|
: msg.m == 'frame' ? (source.frame ? await source.frame(msg.a[0], msg.a[1]) : null)
|
|
23
72
|
: undefined;
|
|
24
73
|
if (!closed)
|
|
25
|
-
channel.send(
|
|
74
|
+
channel.send(stringifyMessage({ t: 'res', id: msg.id, ok: true, v: v ?? null }));
|
|
26
75
|
}
|
|
27
76
|
catch (e) {
|
|
28
77
|
if (!closed)
|
|
29
|
-
channel.send(
|
|
78
|
+
channel.send(stringifyMessage({ t: 'res', id: msg.id, ok: false, e: String(e) }));
|
|
30
79
|
}
|
|
31
80
|
}
|
|
32
81
|
const offMsg = channel.onMessage(function onReplayChannelMessage(raw) {
|
|
@@ -34,7 +83,7 @@ function serveReplayChannel(source, channel) {
|
|
|
34
83
|
return;
|
|
35
84
|
let msg;
|
|
36
85
|
try {
|
|
37
|
-
msg =
|
|
86
|
+
msg = parseMessage(raw);
|
|
38
87
|
}
|
|
39
88
|
catch {
|
|
40
89
|
return;
|
|
@@ -42,7 +91,7 @@ function serveReplayChannel(source, channel) {
|
|
|
42
91
|
if (msg?.t == 'sub' && !lineOff) {
|
|
43
92
|
lineOff = source.line.on(function forwardEnvelope(ev) {
|
|
44
93
|
if (!closed)
|
|
45
|
-
channel.send(
|
|
94
|
+
channel.send(stringifyMessage({ t: 'ev', ev }));
|
|
46
95
|
});
|
|
47
96
|
return;
|
|
48
97
|
}
|
|
@@ -70,7 +119,7 @@ function channelReplayRemote(channel) {
|
|
|
70
119
|
channel.onMessage(function onRemoteChannelMessage(raw) {
|
|
71
120
|
let msg;
|
|
72
121
|
try {
|
|
73
|
-
msg =
|
|
122
|
+
msg = parseMessage(raw);
|
|
74
123
|
}
|
|
75
124
|
catch {
|
|
76
125
|
return;
|
|
@@ -108,7 +157,7 @@ function channelReplayRemote(channel) {
|
|
|
108
157
|
return new Promise((resolve, reject) => {
|
|
109
158
|
const id = nextId++;
|
|
110
159
|
pending.set(id, { resolve, reject });
|
|
111
|
-
channel.send(
|
|
160
|
+
channel.send(stringifyMessage({ t: 'req', id, m, a }));
|
|
112
161
|
});
|
|
113
162
|
}
|
|
114
163
|
return {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.replayRouteSubscribe = replayRouteSubscribe;
|
|
4
|
+
const transport_lifecycle_1 = require("./transport-lifecycle");
|
|
4
5
|
function unsubscribeHandle(handle) {
|
|
5
6
|
if (typeof handle == 'function') {
|
|
6
7
|
handle();
|
|
@@ -50,7 +51,8 @@ function replayRouteSubscribe(remote, cb, opts = {}) {
|
|
|
50
51
|
let replaying = true;
|
|
51
52
|
let lineError;
|
|
52
53
|
const queue = [];
|
|
53
|
-
const
|
|
54
|
+
const frameLineState = (0, transport_lifecycle_1.getRpcMemberState)(nextRemote, 'frameLine');
|
|
55
|
+
const liveLine = policy == 'frame' && frameLineState != false && nextRemote.frameLine ? nextRemote.frameLine : nextRemote.line;
|
|
54
56
|
const handle = liveLine.on(function liveTap(ev) {
|
|
55
57
|
if (slotClosed)
|
|
56
58
|
return;
|
|
@@ -76,7 +78,8 @@ function replayRouteSubscribe(remote, cb, opts = {}) {
|
|
|
76
78
|
async function catchUp() {
|
|
77
79
|
try {
|
|
78
80
|
let done = false;
|
|
79
|
-
|
|
81
|
+
const frameState = (0, transport_lifecycle_1.getRpcMemberState)(nextRemote, 'frame');
|
|
82
|
+
if (since >= 0 && frameState != false && nextRemote.frame) {
|
|
80
83
|
const envs = await nextRemote.frame(since, hint);
|
|
81
84
|
if (slotClosed)
|
|
82
85
|
return;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.exposeReplay = exposeReplay;
|
|
4
4
|
exports.replaySubscribe = replaySubscribe;
|
|
5
5
|
const replay_conflate_1 = require("./replay-conflate");
|
|
6
|
+
const transport_lifecycle_1 = require("./transport-lifecycle");
|
|
6
7
|
function exposeReplayPlain(replay) {
|
|
7
8
|
return {
|
|
8
9
|
line: replay.line,
|
|
@@ -29,10 +30,21 @@ function unsubscribeHandle(handle) {
|
|
|
29
30
|
}
|
|
30
31
|
function replaySubscribe(remote, cb, opts = {}) {
|
|
31
32
|
const { since = -1, onSeq, onError, staleMs, onStale, skewMs = 0, now = Date.now, policy = 'queue', hint } = opts;
|
|
33
|
+
const lifecycle = (0, transport_lifecycle_1.getRpcTransportLifecycle)(remote);
|
|
32
34
|
let lastDelivered = since;
|
|
33
35
|
let replaying = true;
|
|
34
36
|
let closed = false;
|
|
37
|
+
let recoveryGeneration = 0;
|
|
35
38
|
const queue = [];
|
|
39
|
+
let readySettled = false;
|
|
40
|
+
let resolveReady = function resolveReadyLater() { };
|
|
41
|
+
const ready = new Promise(function waitUntilReady(resolve) { resolveReady = resolve; });
|
|
42
|
+
function settleReady() {
|
|
43
|
+
if (readySettled)
|
|
44
|
+
return;
|
|
45
|
+
readySettled = true;
|
|
46
|
+
resolveReady();
|
|
47
|
+
}
|
|
36
48
|
let lastTs = 0;
|
|
37
49
|
let lastArrival = now();
|
|
38
50
|
let staleFlag = false;
|
|
@@ -95,17 +107,68 @@ function replaySubscribe(remote, cb, opts = {}) {
|
|
|
95
107
|
if (!replaying)
|
|
96
108
|
assessStale();
|
|
97
109
|
}
|
|
98
|
-
|
|
99
|
-
|
|
110
|
+
function deliverSorted(events, allowReset) {
|
|
111
|
+
const sorted = [...events].sort((a, b) => a.seq - b.seq);
|
|
112
|
+
if (allowReset && sorted.length && sorted[0].seq <= lastDelivered) {
|
|
113
|
+
lastDelivered = sorted[0].seq - 1;
|
|
114
|
+
}
|
|
115
|
+
for (const event of sorted)
|
|
116
|
+
deliver(event);
|
|
117
|
+
}
|
|
118
|
+
function drainLiveQueue() {
|
|
119
|
+
while (queue.length) {
|
|
120
|
+
const batch = queue.splice(0).sort((a, b) => a.seq - b.seq);
|
|
121
|
+
for (const event of batch)
|
|
122
|
+
deliver(event);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const frameLineState = (0, transport_lifecycle_1.getRpcMemberState)(remote, 'frameLine');
|
|
126
|
+
const liveLine = policy == 'frame' && frameLineState != false && remote.frameLine ? remote.frameLine : remote.line;
|
|
127
|
+
let handle = null;
|
|
128
|
+
let offConnect = function noConnectListener() { };
|
|
129
|
+
let offDisconnect = function noDisconnectListener() { };
|
|
130
|
+
let offClose = function noCloseListener() { };
|
|
131
|
+
function closeSubscription() {
|
|
132
|
+
if (closed)
|
|
133
|
+
return false;
|
|
134
|
+
closed = true;
|
|
135
|
+
recoveryGeneration++;
|
|
136
|
+
queue.length = 0;
|
|
137
|
+
stopStaleTimer();
|
|
138
|
+
offConnect();
|
|
139
|
+
offDisconnect();
|
|
140
|
+
offClose();
|
|
141
|
+
unsubscribeHandle(handle);
|
|
142
|
+
settleReady();
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
function errorText(error) {
|
|
146
|
+
if (typeof error?.message == 'string')
|
|
147
|
+
return error.message;
|
|
148
|
+
if (typeof error?.error?.message == 'string')
|
|
149
|
+
return error.error.message;
|
|
150
|
+
return String(error);
|
|
151
|
+
}
|
|
152
|
+
function failRecovery(error, point, phase) {
|
|
153
|
+
const wrapped = new Error('replaySubscribe: ' + phase + ' from seq ' + point + ' failed: ' + errorText(error));
|
|
154
|
+
wrapped.cause = error;
|
|
155
|
+
if (!closeSubscription())
|
|
156
|
+
return;
|
|
157
|
+
if (onError) {
|
|
158
|
+
try {
|
|
159
|
+
onError(wrapped);
|
|
160
|
+
}
|
|
161
|
+
catch (caught) {
|
|
162
|
+
setTimeout(function rethrowOnError() { throw caught; }, 0);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
setTimeout(function rethrowRecoveryError() { throw wrapped; }, 0);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
handle = liveLine.on(function liveTap(ev) {
|
|
100
170
|
if (ev == null || typeof ev.seq != 'number') {
|
|
101
|
-
|
|
102
|
-
return;
|
|
103
|
-
const err = new Error('replaySubscribe: line ended by server (' + String(ev) + ')');
|
|
104
|
-
off();
|
|
105
|
-
if (onError)
|
|
106
|
-
onError(err);
|
|
107
|
-
else
|
|
108
|
-
setTimeout(function rethrowLineEnd() { throw err; }, 0);
|
|
171
|
+
failRecovery(new Error('line ended by server (' + String(ev) + ')'), lastDelivered, 'live line');
|
|
109
172
|
return;
|
|
110
173
|
}
|
|
111
174
|
lastArrival = now();
|
|
@@ -114,64 +177,91 @@ function replaySubscribe(remote, cb, opts = {}) {
|
|
|
114
177
|
else
|
|
115
178
|
deliver(ev);
|
|
116
179
|
});
|
|
117
|
-
|
|
180
|
+
if (typeof handle?.then == 'function') {
|
|
181
|
+
handle.then(function logicalLineEnded() {
|
|
182
|
+
if (!closed)
|
|
183
|
+
failRecovery(new Error('logical RPC line ended'), lastDelivered, 'live line');
|
|
184
|
+
}, function logicalLineFailed(error) {
|
|
185
|
+
if (!closed)
|
|
186
|
+
failRecovery(error, lastDelivered, 'live line');
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function isCurrent(generation) {
|
|
190
|
+
return !closed && generation == recoveryGeneration;
|
|
191
|
+
}
|
|
192
|
+
async function catchUp(generation, point, initial) {
|
|
118
193
|
try {
|
|
119
194
|
let done = false;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
195
|
+
const frameState = (0, transport_lifecycle_1.getRpcMemberState)(remote, 'frame');
|
|
196
|
+
if (point >= 0 && frameState != false && remote.frame) {
|
|
197
|
+
const envelopes = await remote.frame(point, hint);
|
|
198
|
+
if (!isCurrent(generation))
|
|
123
199
|
return;
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
lastDelivered = envs[0].seq - 1;
|
|
127
|
-
for (const ev of envs)
|
|
128
|
-
deliver(ev);
|
|
129
|
-
}
|
|
200
|
+
if (envelopes) {
|
|
201
|
+
deliverSorted(envelopes, initial);
|
|
130
202
|
done = true;
|
|
131
203
|
}
|
|
132
204
|
}
|
|
133
205
|
if (!done) {
|
|
134
|
-
const tail =
|
|
135
|
-
if (
|
|
206
|
+
const tail = point >= 0 ? await remote.since(point) : null;
|
|
207
|
+
if (!isCurrent(generation))
|
|
136
208
|
return;
|
|
137
209
|
if (tail) {
|
|
138
|
-
|
|
139
|
-
deliver(ev);
|
|
210
|
+
deliverSorted(tail, false);
|
|
140
211
|
}
|
|
141
212
|
else {
|
|
142
|
-
const
|
|
143
|
-
if (
|
|
213
|
+
const keyframe = await remote.keyframe();
|
|
214
|
+
if (!isCurrent(generation))
|
|
144
215
|
return;
|
|
145
|
-
if (
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
216
|
+
if (keyframe) {
|
|
217
|
+
if (initial && keyframe.seq <= lastDelivered)
|
|
218
|
+
lastDelivered = keyframe.seq - 1;
|
|
219
|
+
deliver(keyframe);
|
|
220
|
+
}
|
|
221
|
+
else if (point >= 0) {
|
|
222
|
+
throw new Error('journal evicted or unavailable; no keyframe can cover the gap');
|
|
151
223
|
}
|
|
152
224
|
}
|
|
153
225
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
onError(e);
|
|
158
|
-
else
|
|
159
|
-
setTimeout(function rethrowCatchUp() { throw e; }, 0);
|
|
160
|
-
}
|
|
161
|
-
finally {
|
|
162
|
-
while (queue.length)
|
|
163
|
-
deliver(queue.shift());
|
|
226
|
+
if (!isCurrent(generation))
|
|
227
|
+
return;
|
|
228
|
+
drainLiveQueue();
|
|
164
229
|
replaying = false;
|
|
165
230
|
assessStale();
|
|
231
|
+
settleReady();
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
if (!isCurrent(generation))
|
|
235
|
+
return;
|
|
236
|
+
failRecovery(error, point, initial ? 'initial catch-up' : 'reconnect catch-up');
|
|
166
237
|
}
|
|
167
238
|
}
|
|
168
|
-
|
|
169
|
-
function off() {
|
|
239
|
+
function startCatchUp(initial) {
|
|
170
240
|
if (closed)
|
|
171
241
|
return;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
242
|
+
replaying = true;
|
|
243
|
+
const generation = ++recoveryGeneration;
|
|
244
|
+
void catchUp(generation, lastDelivered, initial);
|
|
245
|
+
}
|
|
246
|
+
if (lifecycle) {
|
|
247
|
+
offDisconnect = lifecycle.onDisconnect(function replayTransportDisconnected() {
|
|
248
|
+
if (closed)
|
|
249
|
+
return;
|
|
250
|
+
replaying = true;
|
|
251
|
+
recoveryGeneration++;
|
|
252
|
+
queue.length = 0;
|
|
253
|
+
});
|
|
254
|
+
offConnect = lifecycle.onConnect(function replayTransportConnected() {
|
|
255
|
+
startCatchUp(!readySettled);
|
|
256
|
+
});
|
|
257
|
+
offClose = lifecycle.onClose(function replayTransportClosed() {
|
|
258
|
+
closeSubscription();
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
if (!lifecycle || lifecycle.connected())
|
|
262
|
+
startCatchUp(true);
|
|
263
|
+
function off() {
|
|
264
|
+
closeSubscription();
|
|
175
265
|
}
|
|
176
266
|
return Object.assign(off, {
|
|
177
267
|
ready,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const RPC_TRANSPORT_LIFECYCLE: unique symbol;
|
|
2
|
+
export declare const RPC_TRANSPORT_CONTROL: unique symbol;
|
|
3
|
+
export declare const RPC_MEMBER_LOOKUP: unique symbol;
|
|
4
|
+
export type RpcMemberLookup = (member: string) => boolean | undefined;
|
|
5
|
+
export declare function getRpcMemberState(remote: any, member: string): boolean | undefined;
|
|
6
|
+
export type TransportLifecycleApi = {
|
|
7
|
+
connected: () => boolean;
|
|
8
|
+
closed: () => boolean;
|
|
9
|
+
generation: () => number;
|
|
10
|
+
onConnect: (cb: (generation: number) => void) => () => void;
|
|
11
|
+
onDisconnect: (cb: (reason: string, generation: number) => void) => () => void;
|
|
12
|
+
onClose: (cb: (reason: string) => void) => () => void;
|
|
13
|
+
};
|
|
14
|
+
export declare function getRpcTransportLifecycle(remote: any): TransportLifecycleApi | undefined;
|
|
15
|
+
export type TransportLifecycleControl = {
|
|
16
|
+
connect: () => void;
|
|
17
|
+
disconnect: (reason: string) => void;
|
|
18
|
+
close: (reason: string) => void;
|
|
19
|
+
};
|
|
20
|
+
export declare function createTransportLifecycle(initialConnected?: boolean): {
|
|
21
|
+
api: TransportLifecycleApi;
|
|
22
|
+
control: TransportLifecycleControl;
|
|
23
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RPC_MEMBER_LOOKUP = exports.RPC_TRANSPORT_CONTROL = exports.RPC_TRANSPORT_LIFECYCLE = void 0;
|
|
4
|
+
exports.getRpcMemberState = getRpcMemberState;
|
|
5
|
+
exports.getRpcTransportLifecycle = getRpcTransportLifecycle;
|
|
6
|
+
exports.createTransportLifecycle = createTransportLifecycle;
|
|
7
|
+
exports.RPC_TRANSPORT_LIFECYCLE = Symbol.for('wenay-common2.rpc.transportLifecycle');
|
|
8
|
+
exports.RPC_TRANSPORT_CONTROL = Symbol.for('wenay-common2.rpc.transportControl');
|
|
9
|
+
exports.RPC_MEMBER_LOOKUP = Symbol.for('wenay-common2.rpc.memberLookup');
|
|
10
|
+
function getRpcMemberState(remote, member) {
|
|
11
|
+
let lookup;
|
|
12
|
+
try {
|
|
13
|
+
const candidate = remote?.[exports.RPC_MEMBER_LOOKUP];
|
|
14
|
+
if (typeof candidate != 'function')
|
|
15
|
+
return undefined;
|
|
16
|
+
if (Object.getOwnPropertyDescriptor(candidate, exports.RPC_MEMBER_LOOKUP)?.value != true)
|
|
17
|
+
return undefined;
|
|
18
|
+
lookup = candidate;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
return lookup(member);
|
|
24
|
+
}
|
|
25
|
+
function getRpcTransportLifecycle(remote) {
|
|
26
|
+
try {
|
|
27
|
+
const candidate = remote?.[exports.RPC_TRANSPORT_LIFECYCLE];
|
|
28
|
+
if (candidate == null || (typeof candidate != 'object' && typeof candidate != 'function'))
|
|
29
|
+
return undefined;
|
|
30
|
+
if (Object.getOwnPropertyDescriptor(candidate, exports.RPC_TRANSPORT_LIFECYCLE)?.value != true)
|
|
31
|
+
return undefined;
|
|
32
|
+
return candidate;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function createTransportLifecycle(initialConnected = true) {
|
|
39
|
+
let online = initialConnected;
|
|
40
|
+
let terminal = false;
|
|
41
|
+
let generation = initialConnected ? 1 : 0;
|
|
42
|
+
const connectCbs = new Set();
|
|
43
|
+
const disconnectCbs = new Set();
|
|
44
|
+
const closeCbs = new Set();
|
|
45
|
+
function onConnect(cb) {
|
|
46
|
+
connectCbs.add(cb);
|
|
47
|
+
return function offConnect() { connectCbs.delete(cb); };
|
|
48
|
+
}
|
|
49
|
+
function onDisconnect(cb) {
|
|
50
|
+
disconnectCbs.add(cb);
|
|
51
|
+
return function offDisconnect() { disconnectCbs.delete(cb); };
|
|
52
|
+
}
|
|
53
|
+
function onClose(cb) {
|
|
54
|
+
closeCbs.add(cb);
|
|
55
|
+
return function offClose() { closeCbs.delete(cb); };
|
|
56
|
+
}
|
|
57
|
+
function connect() {
|
|
58
|
+
if (terminal || online)
|
|
59
|
+
return;
|
|
60
|
+
online = true;
|
|
61
|
+
generation++;
|
|
62
|
+
for (const cb of [...connectCbs])
|
|
63
|
+
cb(generation);
|
|
64
|
+
}
|
|
65
|
+
function disconnect(reason) {
|
|
66
|
+
if (terminal || !online)
|
|
67
|
+
return;
|
|
68
|
+
online = false;
|
|
69
|
+
for (const cb of [...disconnectCbs])
|
|
70
|
+
cb(reason, generation);
|
|
71
|
+
}
|
|
72
|
+
function close(reason) {
|
|
73
|
+
if (terminal)
|
|
74
|
+
return;
|
|
75
|
+
terminal = true;
|
|
76
|
+
online = false;
|
|
77
|
+
for (const cb of [...closeCbs])
|
|
78
|
+
cb(reason);
|
|
79
|
+
connectCbs.clear();
|
|
80
|
+
disconnectCbs.clear();
|
|
81
|
+
closeCbs.clear();
|
|
82
|
+
}
|
|
83
|
+
const api = {
|
|
84
|
+
connected: () => online,
|
|
85
|
+
closed: () => terminal,
|
|
86
|
+
generation: () => generation,
|
|
87
|
+
onConnect,
|
|
88
|
+
onDisconnect,
|
|
89
|
+
onClose,
|
|
90
|
+
};
|
|
91
|
+
Object.defineProperty(api, exports.RPC_TRANSPORT_LIFECYCLE, { value: true });
|
|
92
|
+
const control = { connect, disconnect, close };
|
|
93
|
+
return { api, control };
|
|
94
|
+
}
|