@ai-ecoverse/sliccy-browser 0.1.0 → 0.1.1
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 +2 -2
- package/src/browser.js +112 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/sliccy-browser",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "sliccy:browser for jsh, over CDP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -29,6 +29,6 @@
|
|
|
29
29
|
"src"
|
|
30
30
|
],
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@ai-ecoverse/slicc-cdp": "1.
|
|
32
|
+
"@ai-ecoverse/slicc-cdp": "1.19.1"
|
|
33
33
|
}
|
|
34
34
|
}
|
package/src/browser.js
CHANGED
|
@@ -13,6 +13,8 @@ const routers = new Set();
|
|
|
13
13
|
const subscribers = new Map();
|
|
14
14
|
let seq = 1;
|
|
15
15
|
let listening = false;
|
|
16
|
+
let active = 0;
|
|
17
|
+
let idle;
|
|
16
18
|
|
|
17
19
|
function openClient() {
|
|
18
20
|
if (!opening) {
|
|
@@ -24,6 +26,37 @@ function openClient() {
|
|
|
24
26
|
return opening;
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
async function using(work) {
|
|
30
|
+
active += 1;
|
|
31
|
+
clearTimeout(idle);
|
|
32
|
+
idle = undefined;
|
|
33
|
+
try {
|
|
34
|
+
return await work();
|
|
35
|
+
} finally {
|
|
36
|
+
active -= 1;
|
|
37
|
+
release();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function release() {
|
|
42
|
+
if (active > 0 || subscribers.size > 0 || !opening || idle) return;
|
|
43
|
+
idle = setTimeout(closeIdle, 0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function closeIdle() {
|
|
47
|
+
idle = undefined;
|
|
48
|
+
if (active > 0 || subscribers.size > 0 || !opening) return;
|
|
49
|
+
const closing = opening;
|
|
50
|
+
opening = undefined;
|
|
51
|
+
sessions.clear();
|
|
52
|
+
routers.clear();
|
|
53
|
+
listening = false;
|
|
54
|
+
closing.then(
|
|
55
|
+
(client) => client.close(),
|
|
56
|
+
() => undefined
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
27
60
|
function ignoreMethod(prefix) {
|
|
28
61
|
return (error) => {
|
|
29
62
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -113,7 +146,11 @@ async function listTabs() {
|
|
|
113
146
|
}));
|
|
114
147
|
}
|
|
115
148
|
|
|
116
|
-
export async function findTab(query
|
|
149
|
+
export async function findTab(query) {
|
|
150
|
+
return using(() => findTabNow(query));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function findTabNow(query = {}) {
|
|
117
154
|
const domain = typeof query.domain === 'string' ? query.domain : undefined;
|
|
118
155
|
const urlMatch = patternOf(query.urlMatch);
|
|
119
156
|
if (domain === undefined && urlMatch === undefined) {
|
|
@@ -128,7 +165,11 @@ export async function findTab(query = {}) {
|
|
|
128
165
|
return tabs.find((tab) => re.test(tab.url)) ?? null;
|
|
129
166
|
}
|
|
130
167
|
|
|
131
|
-
export async function ensureTab(url, options
|
|
168
|
+
export async function ensureTab(url, options) {
|
|
169
|
+
return using(() => ensureTabNow(url, options));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function ensureTabNow(url, options = {}) {
|
|
132
173
|
if (typeof url !== 'string' || url.length === 0) {
|
|
133
174
|
throw new TypeError('browser.ensureTab: url must be a string');
|
|
134
175
|
}
|
|
@@ -146,13 +187,14 @@ export async function ensureTab(url, options = {}) {
|
|
|
146
187
|
}
|
|
147
188
|
}
|
|
148
189
|
const client = await openClient();
|
|
149
|
-
const created = await client.send('Target.createTarget', { url });
|
|
190
|
+
const created = await client.send('Target.createTarget', { url: 'about:blank' });
|
|
150
191
|
const targetId = created?.targetId;
|
|
151
192
|
if (typeof targetId !== 'string' || targetId.length === 0) {
|
|
152
193
|
throw new Error('Target.createTarget returned no targetId');
|
|
153
194
|
}
|
|
154
195
|
const { sessionId } = await sessionFor(targetId);
|
|
155
|
-
await waitForLoad(client, sessionId);
|
|
196
|
+
if (url === 'about:blank') await waitForLoad(client, sessionId);
|
|
197
|
+
else await navigateAndWait(client, sessionId, url);
|
|
156
198
|
const live = (await listTabs()).find((tab) => tab.targetId === targetId);
|
|
157
199
|
return live ?? { targetId, url, title: '' };
|
|
158
200
|
}
|
|
@@ -167,7 +209,11 @@ function evalSourceAsync(tab, source) {
|
|
|
167
209
|
|
|
168
210
|
export { evalSource as eval, evalSourceAsync as evalAsync };
|
|
169
211
|
|
|
170
|
-
|
|
212
|
+
function evaluate(tab, source, awaitPromise) {
|
|
213
|
+
return using(() => evaluateNow(tab, source, awaitPromise));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function evaluateNow(tab, source, awaitPromise) {
|
|
171
217
|
const { client, sessionId } = await sessionFor(targetIdOf(tab));
|
|
172
218
|
const result = await client.send(
|
|
173
219
|
'Runtime.evaluate',
|
|
@@ -186,6 +232,10 @@ async function evaluate(tab, source, awaitPromise) {
|
|
|
186
232
|
|
|
187
233
|
export async function cookie(tab, name) {
|
|
188
234
|
if (typeof name !== 'string') throw new TypeError('browser.cookie: name must be a string');
|
|
235
|
+
return using(() => cookieNow(tab, name));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function cookieNow(tab, name) {
|
|
189
239
|
const { client, sessionId } = await sessionFor(targetIdOf(tab));
|
|
190
240
|
const result = await client.send('Network.getCookies', {}, sessionId);
|
|
191
241
|
const cookies = Array.isArray(result?.cookies) ? result.cookies : [];
|
|
@@ -284,6 +334,34 @@ function pageIsSettled(client, sessionId) {
|
|
|
284
334
|
});
|
|
285
335
|
}
|
|
286
336
|
|
|
337
|
+
function navigateAndWait(client, sessionId, url) {
|
|
338
|
+
return new Promise((resolve, reject) => {
|
|
339
|
+
let settled = false;
|
|
340
|
+
let timer;
|
|
341
|
+
const finish = (error) => {
|
|
342
|
+
if (settled) return;
|
|
343
|
+
settled = true;
|
|
344
|
+
clearTimeout(timer);
|
|
345
|
+
client.off('Page.loadEventFired', listener);
|
|
346
|
+
if (error) reject(error);
|
|
347
|
+
else resolve();
|
|
348
|
+
};
|
|
349
|
+
const listener = (message) => {
|
|
350
|
+
if (typeof message?.sessionId === 'string' && message.sessionId !== sessionId) return;
|
|
351
|
+
finish();
|
|
352
|
+
};
|
|
353
|
+
client.on('Page.loadEventFired', listener);
|
|
354
|
+
timer = setTimeout(() => finish(), LOAD_TIMEOUT_MS);
|
|
355
|
+
client.send('Page.navigate', { url }, sessionId).then(
|
|
356
|
+
(result) => {
|
|
357
|
+
if (result?.errorText) finish(new Error(`navigate: ${result.errorText}`));
|
|
358
|
+
else if (!result?.loaderId) finish();
|
|
359
|
+
},
|
|
360
|
+
(error) => finish(error)
|
|
361
|
+
);
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
287
365
|
function waitForLoad(client, sessionId) {
|
|
288
366
|
return new Promise((resolve, reject) => {
|
|
289
367
|
let settled = false;
|
|
@@ -396,9 +474,11 @@ function makeHandle(sub) {
|
|
|
396
474
|
: patch.urlMatch;
|
|
397
475
|
}
|
|
398
476
|
if (patch.filter !== undefined) wire.filter = patch.filter;
|
|
399
|
-
await
|
|
400
|
-
|
|
401
|
-
|
|
477
|
+
await using(() =>
|
|
478
|
+
callRouter(
|
|
479
|
+
sub.targetId,
|
|
480
|
+
`globalThis.__sliccWsRouter.update(${JSON.stringify(sub.id)}, ${JSON.stringify(wire)})`
|
|
481
|
+
)
|
|
402
482
|
);
|
|
403
483
|
if (wire.urlMatch === null) delete sub.urlMatch;
|
|
404
484
|
else if (wire.urlMatch !== undefined) sub.urlMatch = wire.urlMatch;
|
|
@@ -409,9 +489,8 @@ function makeHandle(sub) {
|
|
|
409
489
|
async close() {
|
|
410
490
|
if (!subscribers.has(sub.id)) return false;
|
|
411
491
|
subscribers.delete(sub.id);
|
|
412
|
-
await
|
|
413
|
-
sub.targetId,
|
|
414
|
-
`globalThis.__sliccWsRouter.unregister(${JSON.stringify(sub.id)})`
|
|
492
|
+
await using(() =>
|
|
493
|
+
callRouter(sub.targetId, `globalThis.__sliccWsRouter.unregister(${JSON.stringify(sub.id)})`)
|
|
415
494
|
);
|
|
416
495
|
return true;
|
|
417
496
|
},
|
|
@@ -452,27 +531,30 @@ export const websocket = {
|
|
|
452
531
|
},
|
|
453
532
|
async forward(sink) {
|
|
454
533
|
validateSink(sink);
|
|
455
|
-
|
|
456
|
-
const id = `wssub-${seq++}-${Date.now().toString(36)}`;
|
|
457
|
-
const sub = {
|
|
458
|
-
id,
|
|
459
|
-
targetId,
|
|
460
|
-
forward: sink,
|
|
461
|
-
createdAt: new Date().toISOString(),
|
|
462
|
-
};
|
|
463
|
-
if (urlMatch !== undefined) sub.urlMatch = urlMatch;
|
|
464
|
-
if (selector !== undefined) sub.filter = selector;
|
|
465
|
-
const registered = { id };
|
|
466
|
-
if (urlMatch !== undefined) registered.urlMatch = urlMatch;
|
|
467
|
-
if (selector !== undefined) registered.filter = selector;
|
|
468
|
-
await callRouter(
|
|
469
|
-
targetId,
|
|
470
|
-
`globalThis.__sliccWsRouter.register(${JSON.stringify(registered)})`
|
|
471
|
-
);
|
|
472
|
-
subscribers.set(id, sub);
|
|
473
|
-
return makeHandle(sub);
|
|
534
|
+
return using(() => subscribe(sink));
|
|
474
535
|
},
|
|
475
536
|
};
|
|
537
|
+
async function subscribe(sink) {
|
|
538
|
+
await installRouter(targetId);
|
|
539
|
+
const id = `wssub-${seq++}-${Date.now().toString(36)}`;
|
|
540
|
+
const sub = {
|
|
541
|
+
id,
|
|
542
|
+
targetId,
|
|
543
|
+
forward: sink,
|
|
544
|
+
createdAt: new Date().toISOString(),
|
|
545
|
+
};
|
|
546
|
+
if (urlMatch !== undefined) sub.urlMatch = urlMatch;
|
|
547
|
+
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
|
+
subscribers.set(id, sub);
|
|
556
|
+
return makeHandle(sub);
|
|
557
|
+
}
|
|
476
558
|
return builder;
|
|
477
559
|
},
|
|
478
560
|
async list() {
|