@ai-ecoverse/sliccy-browser 0.1.2 → 0.1.3
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/package.json +1 -1
- package/src/browser.js +194 -92
- package/src/ws-router.js +0 -149
package/package.json
CHANGED
package/src/browser.js
CHANGED
|
@@ -2,14 +2,35 @@ import { appendFile } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { connect } from '@ai-ecoverse/slicc-cdp';
|
|
4
4
|
import { buildBrowserFetchScript } from './fetch-script.js';
|
|
5
|
-
import { routerSource } from './ws-router.js';
|
|
6
5
|
|
|
7
|
-
const BINDING = '__sliccWsRouterReport';
|
|
8
6
|
const LOAD_TIMEOUT_MS = 15000;
|
|
7
|
+
const HELD_FRAMES = 100;
|
|
8
|
+
const RECORDER = `(() => {
|
|
9
|
+
if (globalThis.__sliccWsSent) return true;
|
|
10
|
+
const sent = [];
|
|
11
|
+
const send = WebSocket.prototype.send;
|
|
12
|
+
WebSocket.prototype.send = function (data) {
|
|
13
|
+
if (typeof data === 'string') {
|
|
14
|
+
sent.push([this.url, data]);
|
|
15
|
+
if (sent.length > 64) sent.shift();
|
|
16
|
+
}
|
|
17
|
+
return send.call(this, data);
|
|
18
|
+
};
|
|
19
|
+
const take = (data) => {
|
|
20
|
+
const at = sent.findIndex((entry) => entry[1] === data);
|
|
21
|
+
return at < 0 ? null : sent.splice(at, 1)[0][0];
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(globalThis, '__sliccWsSent', { value: Object.freeze({ take }) });
|
|
24
|
+
return true;
|
|
25
|
+
})()`;
|
|
9
26
|
|
|
10
27
|
let opening;
|
|
11
28
|
const sessions = new Map();
|
|
12
|
-
const
|
|
29
|
+
const tabsBySession = new Map();
|
|
30
|
+
const sockets = new Map();
|
|
31
|
+
const held = new Map();
|
|
32
|
+
const recorders = new Map();
|
|
33
|
+
const networkless = new Map();
|
|
13
34
|
const subscribers = new Map();
|
|
14
35
|
let seq = 1;
|
|
15
36
|
let listening = false;
|
|
@@ -49,7 +70,11 @@ function closeIdle() {
|
|
|
49
70
|
const closing = opening;
|
|
50
71
|
opening = undefined;
|
|
51
72
|
sessions.clear();
|
|
52
|
-
|
|
73
|
+
tabsBySession.clear();
|
|
74
|
+
sockets.clear();
|
|
75
|
+
held.clear();
|
|
76
|
+
recorders.clear();
|
|
77
|
+
networkless.clear();
|
|
53
78
|
listening = false;
|
|
54
79
|
closing.then(
|
|
55
80
|
(client) => client.close(),
|
|
@@ -69,30 +94,152 @@ async function sessionFor(targetId) {
|
|
|
69
94
|
const client = await openClient();
|
|
70
95
|
const cached = sessions.get(targetId);
|
|
71
96
|
if (cached) return { client, sessionId: cached };
|
|
97
|
+
listen(client);
|
|
72
98
|
const sessionId = await client.attach(targetId);
|
|
99
|
+
tabsBySession.set(sessionId, targetId);
|
|
73
100
|
await client.send('Page.enable', {}, sessionId).catch(ignoreMethod('Page.enable:'));
|
|
74
101
|
await client.send('Runtime.enable', {}, sessionId).catch(ignoreMethod('Runtime.enable:'));
|
|
75
|
-
await client.send('Network.enable', {}, sessionId).catch(
|
|
102
|
+
await client.send('Network.enable', {}, sessionId).catch((error) => {
|
|
103
|
+
ignoreMethod('Network.enable:')(error);
|
|
104
|
+
networkless.set(sessionId, error.message);
|
|
105
|
+
});
|
|
76
106
|
sessions.set(targetId, sessionId);
|
|
77
|
-
listen(client);
|
|
78
107
|
return { client, sessionId };
|
|
79
108
|
}
|
|
80
109
|
|
|
81
110
|
function listen(client) {
|
|
82
111
|
if (listening) return;
|
|
83
112
|
listening = true;
|
|
84
|
-
client.on('
|
|
85
|
-
const
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (!body || typeof body.subId !== 'string') return;
|
|
94
|
-
void deliver(body.subId, body.payload);
|
|
113
|
+
client.on('Network.webSocketCreated', (message) => {
|
|
114
|
+
const { requestId, url } = message?.params ?? {};
|
|
115
|
+
if (typeof requestId !== 'string' || typeof url !== 'string') return;
|
|
116
|
+
sockets.set(socketKey(message.sessionId, requestId), url);
|
|
117
|
+
});
|
|
118
|
+
client.on('Network.webSocketClosed', (message) => {
|
|
119
|
+
const key = socketKey(message?.sessionId, message?.params?.requestId);
|
|
120
|
+
sockets.delete(key);
|
|
121
|
+
held.delete(key);
|
|
95
122
|
});
|
|
123
|
+
client.on('Network.webSocketFrameReceived', (message) => dispatchFrame(message));
|
|
124
|
+
client.on('Network.webSocketFrameSent', (message) => void learnUrl(client, message));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function socketKey(sessionId, requestId) {
|
|
128
|
+
return `${sessionId ?? ''} ${requestId ?? ''}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function dispatchFrame(message) {
|
|
132
|
+
const params = message?.params;
|
|
133
|
+
const response = params?.response;
|
|
134
|
+
if (response?.opcode !== 1 || typeof response.payloadData !== 'string') return;
|
|
135
|
+
const targetId = tabsBySession.get(message.sessionId);
|
|
136
|
+
if (targetId === undefined) return;
|
|
137
|
+
const key = socketKey(message.sessionId, params.requestId);
|
|
138
|
+
const url = sockets.get(key);
|
|
139
|
+
if (url !== undefined) return route(targetId, url, response.payloadData, () => true);
|
|
140
|
+
route(targetId, url, response.payloadData, (sub) => sub.urlMatch === undefined);
|
|
141
|
+
holdFrame(message.sessionId, targetId, key, response.payloadData);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function route(targetId, url, raw, which) {
|
|
145
|
+
for (const sub of subscribers.values()) {
|
|
146
|
+
if (sub.targetId !== targetId || !which(sub) || !urlMatches(sub, url)) continue;
|
|
147
|
+
const body = parseFrame(raw, sub.filter?.parseAs);
|
|
148
|
+
if (body === undefined || !whereMatches(sub.filter?.where, body)) continue;
|
|
149
|
+
deliver(sub, project(body, sub.filter?.project));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function holdFrame(sessionId, targetId, key, raw) {
|
|
154
|
+
const wanted = [...subscribers.values()].some(
|
|
155
|
+
(sub) => sub.targetId === targetId && sub.urlMatch !== undefined
|
|
156
|
+
);
|
|
157
|
+
if (!wanted) return;
|
|
158
|
+
const frames = held.get(key) ?? [];
|
|
159
|
+
frames.push(raw);
|
|
160
|
+
if (frames.length > HELD_FRAMES) frames.shift();
|
|
161
|
+
held.set(key, frames);
|
|
162
|
+
record(sessionId);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function record(sessionId) {
|
|
166
|
+
if (recorders.has(sessionId)) return recorders.get(sessionId);
|
|
167
|
+
const installing = openClient()
|
|
168
|
+
.then((client) =>
|
|
169
|
+
client.send('Runtime.evaluate', { expression: RECORDER, returnByValue: true }, sessionId)
|
|
170
|
+
)
|
|
171
|
+
.catch(() => undefined);
|
|
172
|
+
recorders.set(sessionId, installing);
|
|
173
|
+
return installing;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function learnUrl(client, message) {
|
|
177
|
+
const sessionId = message?.sessionId;
|
|
178
|
+
const { requestId, response } = message?.params ?? {};
|
|
179
|
+
const key = socketKey(sessionId, requestId);
|
|
180
|
+
if (sockets.has(key) || !recorders.has(sessionId)) return;
|
|
181
|
+
if (response?.opcode !== 1 || typeof response.payloadData !== 'string') return;
|
|
182
|
+
await recorders.get(sessionId);
|
|
183
|
+
const found = await client
|
|
184
|
+
.send(
|
|
185
|
+
'Runtime.evaluate',
|
|
186
|
+
{
|
|
187
|
+
expression: `globalThis.__sliccWsSent?.take(${JSON.stringify(response.payloadData)}) ?? null`,
|
|
188
|
+
returnByValue: true,
|
|
189
|
+
},
|
|
190
|
+
sessionId
|
|
191
|
+
)
|
|
192
|
+
.catch(() => undefined);
|
|
193
|
+
const url = found?.result?.value;
|
|
194
|
+
if (typeof url !== 'string' || sockets.has(key)) return;
|
|
195
|
+
sockets.set(key, url);
|
|
196
|
+
const frames = held.get(key) ?? [];
|
|
197
|
+
held.delete(key);
|
|
198
|
+
const targetId = tabsBySession.get(sessionId);
|
|
199
|
+
for (const raw of frames) route(targetId, url, raw, (sub) => sub.urlMatch !== undefined);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function urlMatches(sub, url) {
|
|
203
|
+
if (sub.urlMatch === undefined) return true;
|
|
204
|
+
return typeof url === 'string' && sub.urlMatchRe.test(url);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function isPlainObject(value) {
|
|
208
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function subsetMatch(value, template) {
|
|
212
|
+
for (const key of Object.keys(template)) {
|
|
213
|
+
const expected = template[key];
|
|
214
|
+
const actual = value[key];
|
|
215
|
+
if (isPlainObject(expected)) {
|
|
216
|
+
if (!isPlainObject(actual) || !subsetMatch(actual, expected)) return false;
|
|
217
|
+
} else if (!Object.is(actual, expected)) {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function whereMatches(where, body) {
|
|
225
|
+
if (!isPlainObject(where) || Object.keys(where).length === 0) return true;
|
|
226
|
+
return isPlainObject(body) && subsetMatch(body, where);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function parseFrame(raw, parseAs) {
|
|
230
|
+
if (parseAs === 'text') return raw;
|
|
231
|
+
try {
|
|
232
|
+
return JSON.parse(raw);
|
|
233
|
+
} catch {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function project(body, fields) {
|
|
239
|
+
if (!Array.isArray(fields) || fields.length === 0 || !isPlainObject(body)) return body;
|
|
240
|
+
const out = {};
|
|
241
|
+
for (const field of fields) if (field in body) out[field] = body[field];
|
|
242
|
+
return out;
|
|
96
243
|
}
|
|
97
244
|
|
|
98
245
|
function targetIdOf(tab) {
|
|
@@ -415,39 +562,6 @@ function validateSink(forward) {
|
|
|
415
562
|
}
|
|
416
563
|
}
|
|
417
564
|
|
|
418
|
-
async function installRouter(targetId) {
|
|
419
|
-
if (routers.has(targetId)) return;
|
|
420
|
-
const { client, sessionId } = await sessionFor(targetId);
|
|
421
|
-
try {
|
|
422
|
-
await client.send('Runtime.addBinding', { name: BINDING }, sessionId);
|
|
423
|
-
} catch (error) {
|
|
424
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
425
|
-
if (!message.includes('already exists')) throw error;
|
|
426
|
-
}
|
|
427
|
-
await client.send('Page.addScriptToEvaluateOnNewDocument', { source: routerSource }, sessionId);
|
|
428
|
-
const installed = await client.send(
|
|
429
|
-
'Runtime.evaluate',
|
|
430
|
-
{ expression: routerSource, returnByValue: true },
|
|
431
|
-
sessionId
|
|
432
|
-
);
|
|
433
|
-
if (installed?.exceptionDetails) {
|
|
434
|
-
throw new Error(exceptionMessage(installed.exceptionDetails));
|
|
435
|
-
}
|
|
436
|
-
routers.add(targetId);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
async function callRouter(targetId, expression) {
|
|
440
|
-
const { client, sessionId } = await sessionFor(targetId);
|
|
441
|
-
const result = await client.send(
|
|
442
|
-
'Runtime.evaluate',
|
|
443
|
-
{ expression, returnByValue: true },
|
|
444
|
-
sessionId
|
|
445
|
-
);
|
|
446
|
-
if (result?.exceptionDetails) {
|
|
447
|
-
throw new Error(exceptionMessage(result.exceptionDetails));
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
565
|
function toInfo(sub) {
|
|
452
566
|
const info = {
|
|
453
567
|
id: sub.id,
|
|
@@ -464,24 +578,14 @@ function makeHandle(sub) {
|
|
|
464
578
|
return {
|
|
465
579
|
...toInfo(sub),
|
|
466
580
|
async update(patch = {}) {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
: patch.urlMatch;
|
|
581
|
+
if (patch.urlMatch === null) {
|
|
582
|
+
delete sub.urlMatch;
|
|
583
|
+
delete sub.urlMatchRe;
|
|
584
|
+
} else if (patch.urlMatch !== undefined) {
|
|
585
|
+
const next = patternOf(patch.urlMatch);
|
|
586
|
+
sub.urlMatchRe = compilePattern(next, 'browser.websocket');
|
|
587
|
+
sub.urlMatch = next;
|
|
475
588
|
}
|
|
476
|
-
if (patch.filter !== undefined) wire.filter = patch.filter;
|
|
477
|
-
await using(() =>
|
|
478
|
-
callRouter(
|
|
479
|
-
sub.targetId,
|
|
480
|
-
`globalThis.__sliccWsRouter.update(${JSON.stringify(sub.id)}, ${JSON.stringify(wire)})`
|
|
481
|
-
)
|
|
482
|
-
);
|
|
483
|
-
if (wire.urlMatch === null) delete sub.urlMatch;
|
|
484
|
-
else if (wire.urlMatch !== undefined) sub.urlMatch = wire.urlMatch;
|
|
485
589
|
if (patch.filter === null) delete sub.filter;
|
|
486
590
|
else if (patch.filter !== undefined) sub.filter = patch.filter;
|
|
487
591
|
return toInfo(sub);
|
|
@@ -489,29 +593,24 @@ function makeHandle(sub) {
|
|
|
489
593
|
async close() {
|
|
490
594
|
if (!subscribers.has(sub.id)) return false;
|
|
491
595
|
subscribers.delete(sub.id);
|
|
492
|
-
await
|
|
493
|
-
|
|
494
|
-
);
|
|
596
|
+
await sub.writing;
|
|
597
|
+
release();
|
|
495
598
|
return true;
|
|
496
599
|
},
|
|
497
600
|
};
|
|
498
601
|
}
|
|
499
602
|
|
|
500
|
-
|
|
501
|
-
const
|
|
502
|
-
if (
|
|
503
|
-
|
|
504
|
-
if (sub.forward.sink === 'log') {
|
|
505
|
-
console.log(typeof payload === 'string' ? payload : JSON.stringify(payload));
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
if (sub.forward.sink === 'vfs') {
|
|
509
|
-
const line = `${typeof payload === 'string' ? payload : JSON.stringify(payload)}\n`;
|
|
510
|
-
await appendFile(sub.forward.path, line);
|
|
511
|
-
}
|
|
512
|
-
} catch {
|
|
603
|
+
function deliver(sub, payload) {
|
|
604
|
+
const line = typeof payload === 'string' ? payload : JSON.stringify(payload);
|
|
605
|
+
if (sub.forward.sink === 'log') {
|
|
606
|
+
console.log(line);
|
|
513
607
|
return;
|
|
514
608
|
}
|
|
609
|
+
if (sub.forward.sink === 'vfs') {
|
|
610
|
+
sub.writing = sub.writing
|
|
611
|
+
.then(() => appendFile(sub.forward.path, `${line}\n`))
|
|
612
|
+
.catch(() => undefined);
|
|
613
|
+
}
|
|
515
614
|
}
|
|
516
615
|
|
|
517
616
|
export const websocket = {
|
|
@@ -535,23 +634,26 @@ export const websocket = {
|
|
|
535
634
|
},
|
|
536
635
|
};
|
|
537
636
|
async function subscribe(sink) {
|
|
538
|
-
|
|
637
|
+
const urlMatchRe =
|
|
638
|
+
urlMatch === undefined ? undefined : compilePattern(urlMatch, 'browser.websocket');
|
|
639
|
+
const { sessionId } = await sessionFor(targetId);
|
|
640
|
+
if (networkless.has(sessionId)) {
|
|
641
|
+
throw new Error(`browser.websocket: ${networkless.get(sessionId)}`);
|
|
642
|
+
}
|
|
643
|
+
if (urlMatch !== undefined) await record(sessionId);
|
|
539
644
|
const id = `wssub-${seq++}-${Date.now().toString(36)}`;
|
|
540
645
|
const sub = {
|
|
541
646
|
id,
|
|
542
647
|
targetId,
|
|
543
648
|
forward: sink,
|
|
544
649
|
createdAt: new Date().toISOString(),
|
|
650
|
+
writing: Promise.resolve(),
|
|
545
651
|
};
|
|
546
|
-
if (urlMatch !== undefined)
|
|
652
|
+
if (urlMatch !== undefined) {
|
|
653
|
+
sub.urlMatch = urlMatch;
|
|
654
|
+
sub.urlMatchRe = urlMatchRe;
|
|
655
|
+
}
|
|
547
656
|
if (selector !== undefined) sub.filter = selector;
|
|
548
|
-
const registered = { id };
|
|
549
|
-
if (urlMatch !== undefined) registered.urlMatch = urlMatch;
|
|
550
|
-
if (selector !== undefined) registered.filter = selector;
|
|
551
|
-
await callRouter(
|
|
552
|
-
targetId,
|
|
553
|
-
`globalThis.__sliccWsRouter.register(${JSON.stringify(registered)})`
|
|
554
|
-
);
|
|
555
657
|
subscribers.set(id, sub);
|
|
556
658
|
return makeHandle(sub);
|
|
557
659
|
}
|
package/src/ws-router.js
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
export function installWsRouter(win) {
|
|
2
|
-
const w = win;
|
|
3
|
-
if (w.__sliccWsRouter) return;
|
|
4
|
-
|
|
5
|
-
const subs = new Map();
|
|
6
|
-
|
|
7
|
-
function compileUrlMatch(pattern) {
|
|
8
|
-
if (pattern === undefined) return undefined;
|
|
9
|
-
try {
|
|
10
|
-
return new RegExp(pattern);
|
|
11
|
-
} catch {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function isPlainObject(value) {
|
|
17
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function subsetMatch(value, template) {
|
|
21
|
-
for (const key of Object.keys(template)) {
|
|
22
|
-
const expected = template[key];
|
|
23
|
-
const actual = value[key];
|
|
24
|
-
if (isPlainObject(expected)) {
|
|
25
|
-
if (!isPlainObject(actual)) return false;
|
|
26
|
-
if (!subsetMatch(actual, expected)) return false;
|
|
27
|
-
continue;
|
|
28
|
-
}
|
|
29
|
-
if (!Object.is(actual, expected)) return false;
|
|
30
|
-
}
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function parseFrame(raw, parseAs) {
|
|
35
|
-
if (parseAs === 'text') return raw;
|
|
36
|
-
try {
|
|
37
|
-
return JSON.parse(raw);
|
|
38
|
-
} catch {
|
|
39
|
-
return undefined;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function project(body, fields) {
|
|
44
|
-
if (!fields || fields.length === 0) return body;
|
|
45
|
-
if (!isPlainObject(body)) return body;
|
|
46
|
-
const out = {};
|
|
47
|
-
for (const field of fields) if (field in body) out[field] = body[field];
|
|
48
|
-
return out;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function report(subId, payload) {
|
|
52
|
-
const reporter = w.__sliccWsRouterReport;
|
|
53
|
-
if (typeof reporter !== 'function') return;
|
|
54
|
-
try {
|
|
55
|
-
reporter(JSON.stringify({ subId, payload }));
|
|
56
|
-
} catch {
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function urlMatches(sub, url) {
|
|
62
|
-
if (sub.urlMatch === undefined) return true;
|
|
63
|
-
const re = sub._urlMatchRe;
|
|
64
|
-
if (re === null) return false;
|
|
65
|
-
return !re || re.test(url);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function whereMatches(sub, body) {
|
|
69
|
-
const where = sub.filter?.where;
|
|
70
|
-
if (!where || Object.keys(where).length === 0) return true;
|
|
71
|
-
if (!isPlainObject(body)) return false;
|
|
72
|
-
return subsetMatch(body, where);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function dispatchFrame(url, raw) {
|
|
76
|
-
if (subs.size === 0) return;
|
|
77
|
-
for (const sub of subs.values()) {
|
|
78
|
-
if (!urlMatches(sub, url)) continue;
|
|
79
|
-
const body = parseFrame(raw, sub.filter?.parseAs);
|
|
80
|
-
if (body === undefined) continue;
|
|
81
|
-
if (!whereMatches(sub, body)) continue;
|
|
82
|
-
report(sub.id, project(body, sub.filter?.project));
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const seen = new WeakSet();
|
|
87
|
-
function wrapInstance(ws) {
|
|
88
|
-
if (seen.has(ws)) return;
|
|
89
|
-
seen.add(ws);
|
|
90
|
-
ws.addEventListener('message', (ev) => {
|
|
91
|
-
if (typeof ev.data !== 'string') return;
|
|
92
|
-
try {
|
|
93
|
-
dispatchFrame(ws.url, ev.data);
|
|
94
|
-
} catch {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const WS = w.WebSocket;
|
|
101
|
-
if (!WS) return;
|
|
102
|
-
const origSend = WS.prototype.send;
|
|
103
|
-
WS.prototype.send = function patchedSend(data) {
|
|
104
|
-
wrapInstance(this);
|
|
105
|
-
return origSend.call(this, data);
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
function applyUrlMatchPatch(next, patch) {
|
|
109
|
-
if (!Object.prototype.hasOwnProperty.call(patch, 'urlMatch')) return;
|
|
110
|
-
if (patch.urlMatch === null) {
|
|
111
|
-
delete next.urlMatch;
|
|
112
|
-
next._urlMatchRe = undefined;
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
if (typeof patch.urlMatch === 'string') {
|
|
116
|
-
next.urlMatch = patch.urlMatch;
|
|
117
|
-
next._urlMatchRe = compileUrlMatch(patch.urlMatch);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function applyFilterPatch(next, patch) {
|
|
122
|
-
if (!Object.prototype.hasOwnProperty.call(patch, 'filter')) return;
|
|
123
|
-
if (patch.filter === null) {
|
|
124
|
-
delete next.filter;
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (patch.filter !== undefined) next.filter = patch.filter;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const router = {
|
|
131
|
-
register(sub) {
|
|
132
|
-
subs.set(sub.id, { ...sub, _urlMatchRe: compileUrlMatch(sub.urlMatch) });
|
|
133
|
-
},
|
|
134
|
-
update(id, patch) {
|
|
135
|
-
const cur = subs.get(id);
|
|
136
|
-
if (!cur) return;
|
|
137
|
-
const next = { ...cur, id };
|
|
138
|
-
applyUrlMatchPatch(next, patch);
|
|
139
|
-
applyFilterPatch(next, patch);
|
|
140
|
-
subs.set(id, next);
|
|
141
|
-
},
|
|
142
|
-
unregister(id) {
|
|
143
|
-
subs.delete(id);
|
|
144
|
-
},
|
|
145
|
-
};
|
|
146
|
-
Object.defineProperty(w, '__sliccWsRouter', { value: router, configurable: false });
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export const routerSource = `(${installWsRouter.toString()})(globalThis);`;
|