@harborclient/sdk 1.4.5 → 1.5.0
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/CHANGELOG.md +4 -0
- package/dist/runtime/createBridgedPluginContext.js +76 -19
- package/dist/runtime/livePageHandle.d.ts +50 -0
- package/dist/runtime/{webpageHandle.js → livePageHandle.js} +125 -47
- package/dist/snippets.d.ts +34 -12
- package/dist/types.d.ts +456 -14
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/runtime/webpageHandle.d.ts +0 -50
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 1.5.0 - 2026-08-01
|
|
8
|
+
|
|
9
|
+
- feat(live-page): introduce live page functionality and refactor related components. (`16f1f304`)
|
|
10
|
+
|
|
7
11
|
## 1.4.5 - 2026-08-01
|
|
8
12
|
|
|
9
13
|
- feat(websites): implement Add Live Page modal and import functionality. (`1bdbf8a4`)
|
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
registerContributionHeaderActions
|
|
6
6
|
} from './contributionRegistry.js';
|
|
7
7
|
import { bridgeInvoke, bridgeOn } from './hcBridge.js';
|
|
8
|
+
import { openLivePage } from './livePageHandle.js';
|
|
8
9
|
import { createPluginDatabaseApi } from './pluginDatabaseApi.js';
|
|
9
10
|
import { setHostReact } from './reactHost.js';
|
|
10
|
-
import { openWebpage } from './webpageHandle.js';
|
|
11
11
|
|
|
12
12
|
/** @type {Map<string, Set<(...args: unknown[]) => void | Promise<void>>>} */
|
|
13
13
|
const commandHandlers = new Map();
|
|
@@ -354,7 +354,7 @@ export function createBridgedPluginContext({ pluginId, mode, contributionId, rea
|
|
|
354
354
|
const assertAi = () => assertPermission('ai');
|
|
355
355
|
|
|
356
356
|
/**
|
|
357
|
-
* Asserts browser permission for embedded
|
|
357
|
+
* Asserts browser permission for embedded live page control.
|
|
358
358
|
*/
|
|
359
359
|
const assertBrowser = () => assertPermission('browser');
|
|
360
360
|
|
|
@@ -364,43 +364,56 @@ export function createBridgedPluginContext({ pluginId, mode, contributionId, rea
|
|
|
364
364
|
const assertLiveServer = () => assertPermission('live-server');
|
|
365
365
|
|
|
366
366
|
/**
|
|
367
|
-
*
|
|
367
|
+
* Asserts live-pages permission for saved live page (website) APIs.
|
|
368
|
+
*/
|
|
369
|
+
const assertLivePages = () => assertPermission('live-pages');
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Invokes a livePage session op on the host renderer via the plugin bridge.
|
|
368
373
|
*
|
|
369
|
-
* @param {Record<string, unknown>} req -
|
|
374
|
+
* @param {Record<string, unknown>} req - ScriptLivePageRequest-shaped payload.
|
|
370
375
|
* @returns {Promise<unknown>} Host session result.
|
|
371
376
|
*/
|
|
372
|
-
const
|
|
377
|
+
const callLivePage = async (req) => {
|
|
373
378
|
const op = String(req.op ?? '');
|
|
374
379
|
switch (op) {
|
|
375
380
|
case 'open':
|
|
376
|
-
return bridgeInvoke('
|
|
381
|
+
return bridgeInvoke('livePage.open', { url: req.url, reuse: req.reuse });
|
|
377
382
|
case 'focus':
|
|
378
|
-
return bridgeInvoke('
|
|
383
|
+
return bridgeInvoke('livePage.focus', { tabId: req.tabId });
|
|
379
384
|
case 'close':
|
|
380
|
-
return bridgeInvoke('
|
|
385
|
+
return bridgeInvoke('livePage.close', { tabId: req.tabId });
|
|
381
386
|
case 'query':
|
|
382
|
-
return bridgeInvoke('
|
|
387
|
+
return bridgeInvoke('livePage.query', {
|
|
383
388
|
tabId: req.tabId,
|
|
384
389
|
selector: req.selector,
|
|
385
390
|
all: req.all,
|
|
386
391
|
maxElements: req.maxElements
|
|
387
392
|
});
|
|
388
393
|
case 'evaluate':
|
|
389
|
-
return bridgeInvoke('
|
|
394
|
+
return bridgeInvoke('livePage.evaluate', {
|
|
390
395
|
tabId: req.tabId,
|
|
391
396
|
expression: req.expression
|
|
392
397
|
});
|
|
393
398
|
case 'injectScript':
|
|
394
|
-
return bridgeInvoke('
|
|
399
|
+
return bridgeInvoke('livePage.injectScript', { tabId: req.tabId, source: req.source });
|
|
395
400
|
case 'injectStylesheet':
|
|
396
|
-
return bridgeInvoke('
|
|
401
|
+
return bridgeInvoke('livePage.injectStylesheet', { tabId: req.tabId, css: req.css });
|
|
397
402
|
case 'screenshot':
|
|
398
|
-
return bridgeInvoke('
|
|
403
|
+
return bridgeInvoke('livePage.screenshot', {
|
|
399
404
|
tabId: req.tabId,
|
|
400
405
|
fullPage: req.fullPage === true
|
|
401
406
|
});
|
|
407
|
+
case 'goBack':
|
|
408
|
+
return bridgeInvoke('livePage.goBack', { tabId: req.tabId });
|
|
409
|
+
case 'goForward':
|
|
410
|
+
return bridgeInvoke('livePage.goForward', { tabId: req.tabId });
|
|
411
|
+
case 'reload':
|
|
412
|
+
return bridgeInvoke('livePage.reload', { tabId: req.tabId });
|
|
413
|
+
case 'navigate':
|
|
414
|
+
return bridgeInvoke('livePage.navigate', { tabId: req.tabId, url: req.url });
|
|
402
415
|
default:
|
|
403
|
-
throw new Error(`Unsupported
|
|
416
|
+
throw new Error(`Unsupported livePage bridge op: ${op}`);
|
|
404
417
|
}
|
|
405
418
|
};
|
|
406
419
|
|
|
@@ -860,6 +873,28 @@ export function createBridgedPluginContext({ pluginId, mode, contributionId, rea
|
|
|
860
873
|
});
|
|
861
874
|
});
|
|
862
875
|
},
|
|
876
|
+
registerLivePageChromeAction: (action) => {
|
|
877
|
+
assertManifestContribution('livePageChromeActions', action.id);
|
|
878
|
+
if (!isAgent) {
|
|
879
|
+
return noopDisposable();
|
|
880
|
+
}
|
|
881
|
+
void bridgeInvoke('registerContribution', {
|
|
882
|
+
kind: 'livePageChromeActions',
|
|
883
|
+
contribution: {
|
|
884
|
+
pluginId,
|
|
885
|
+
id: action.id,
|
|
886
|
+
title: action.title,
|
|
887
|
+
command: action.command,
|
|
888
|
+
icon: action.icon
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
return track(() => {
|
|
892
|
+
void bridgeInvoke('unregisterContribution', {
|
|
893
|
+
kind: 'livePageChromeActions',
|
|
894
|
+
contributionId: action.id
|
|
895
|
+
});
|
|
896
|
+
});
|
|
897
|
+
},
|
|
863
898
|
registerScriptEditorAction: (action) => {
|
|
864
899
|
assertManifestContribution('scriptEditorActions', action.id);
|
|
865
900
|
if (!isAgent) {
|
|
@@ -1341,6 +1376,28 @@ export function createBridgedPluginContext({ pluginId, mode, contributionId, rea
|
|
|
1341
1376
|
});
|
|
1342
1377
|
}
|
|
1343
1378
|
},
|
|
1379
|
+
livePages: {
|
|
1380
|
+
list: async () => {
|
|
1381
|
+
assertLivePages();
|
|
1382
|
+
return bridgeInvoke('livePages.list');
|
|
1383
|
+
},
|
|
1384
|
+
get: async (idOrUuid) => {
|
|
1385
|
+
assertLivePages();
|
|
1386
|
+
return bridgeInvoke('livePages.get', { idOrUuid });
|
|
1387
|
+
},
|
|
1388
|
+
create: async (input) => {
|
|
1389
|
+
assertLivePages();
|
|
1390
|
+
return bridgeInvoke('livePages.create', { input });
|
|
1391
|
+
},
|
|
1392
|
+
update: async (input) => {
|
|
1393
|
+
assertLivePages();
|
|
1394
|
+
return bridgeInvoke('livePages.update', { input });
|
|
1395
|
+
},
|
|
1396
|
+
delete: async (id) => {
|
|
1397
|
+
assertLivePages();
|
|
1398
|
+
await bridgeInvoke('livePages.delete', { id });
|
|
1399
|
+
}
|
|
1400
|
+
},
|
|
1344
1401
|
ai: {
|
|
1345
1402
|
registerChatPointer: (config) => {
|
|
1346
1403
|
assertAi();
|
|
@@ -1375,13 +1432,13 @@ export function createBridgedPluginContext({ pluginId, mode, contributionId, rea
|
|
|
1375
1432
|
/**
|
|
1376
1433
|
* Opens or reuses an embedded browser tab and returns a control handle.
|
|
1377
1434
|
*
|
|
1378
|
-
* Requires the `browser` permission. Same semantics as request-script `hc.
|
|
1435
|
+
* Requires the `browser` permission. Same semantics as request-script `hc.livePage`.
|
|
1379
1436
|
*
|
|
1380
1437
|
* @param {string} [url] - Optional URL; omit to bind the active browser tab.
|
|
1381
1438
|
* @param {{ reuse?: boolean }} [options] - Optional `{ reuse }` (default true).
|
|
1382
|
-
* @returns {Promise<import('../types').
|
|
1439
|
+
* @returns {Promise<import('../types').PluginLivePageHandle>} Webpage handle.
|
|
1383
1440
|
*/
|
|
1384
|
-
|
|
1441
|
+
livePage: async (url, options) => {
|
|
1385
1442
|
assertBrowser();
|
|
1386
1443
|
/**
|
|
1387
1444
|
* Writes screenshot PNG bytes via the plugin filesystem bridge.
|
|
@@ -1394,11 +1451,11 @@ export function createBridgedPluginContext({ pluginId, mode, contributionId, rea
|
|
|
1394
1451
|
assertPermission('filesystem:write');
|
|
1395
1452
|
const result = await bridgeInvoke('fs.writeBytes', { path, base64: pngBase64 });
|
|
1396
1453
|
if (typeof result !== 'string' || !result.trim()) {
|
|
1397
|
-
throw new Error('hc.
|
|
1454
|
+
throw new Error('hc.livePage().screenshot failed to resolve write path');
|
|
1398
1455
|
}
|
|
1399
1456
|
return result;
|
|
1400
1457
|
};
|
|
1401
|
-
return
|
|
1458
|
+
return openLivePage(callLivePage, url, options, writeScreenshotBytes);
|
|
1402
1459
|
}
|
|
1403
1460
|
};
|
|
1404
1461
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { PluginLivePageHandle } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Throws when a live-page bridge result is an `{ error }` object.
|
|
5
|
+
*
|
|
6
|
+
* @param result - Raw bridge result.
|
|
7
|
+
* @returns The result when it is not an error.
|
|
8
|
+
* @throws When the bridge returned `{ error: string }`.
|
|
9
|
+
*/
|
|
10
|
+
export function unwrapLivePageBridgeResult(result: unknown): unknown;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Normalizes the optional second argument to `hc.livePage(url, options)`.
|
|
14
|
+
*
|
|
15
|
+
* @param options - User-provided options.
|
|
16
|
+
* @returns Normalized open options.
|
|
17
|
+
*/
|
|
18
|
+
export function normalizeLivePageOpenOptions(options?: unknown): { reuse?: boolean };
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Builds a live-page handle whose methods call the host live-page bridge.
|
|
22
|
+
*
|
|
23
|
+
* @param tab - Opened tab metadata from the bridge.
|
|
24
|
+
* @param callLivePage - Bridge transport.
|
|
25
|
+
* @returns Plain-object handle for the plugin world.
|
|
26
|
+
*/
|
|
27
|
+
export function createLivePageHandle(
|
|
28
|
+
tab: {
|
|
29
|
+
tabId: string;
|
|
30
|
+
url: string;
|
|
31
|
+
title: string;
|
|
32
|
+
canGoBack?: boolean;
|
|
33
|
+
canGoForward?: boolean;
|
|
34
|
+
},
|
|
35
|
+
callLivePage: (req: Record<string, unknown>) => Promise<unknown>
|
|
36
|
+
): PluginLivePageHandle;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Opens or reuses an embedded browser tab and returns a control handle.
|
|
40
|
+
*
|
|
41
|
+
* @param callLivePage - Bridge transport that accepts ScriptLivePageRequest-shaped payloads.
|
|
42
|
+
* @param url - Optional URL; omit to bind the active browser tab.
|
|
43
|
+
* @param openOptions - Optional `{ reuse }` (default true).
|
|
44
|
+
* @returns Live-page handle.
|
|
45
|
+
*/
|
|
46
|
+
export function openLivePage(
|
|
47
|
+
callLivePage: (req: Record<string, unknown>) => Promise<unknown>,
|
|
48
|
+
url?: unknown,
|
|
49
|
+
openOptions?: unknown
|
|
50
|
+
): Promise<PluginLivePageHandle>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Throws when a
|
|
2
|
+
* Throws when a live-page bridge result is an `{ error }` object.
|
|
3
3
|
*
|
|
4
4
|
* @param {unknown} result - Raw bridge result.
|
|
5
5
|
* @returns {unknown} The result when it is not an error.
|
|
6
6
|
* @throws {Error} When the bridge returned `{ error: string }`.
|
|
7
7
|
*/
|
|
8
|
-
export function
|
|
8
|
+
export function unwrapLivePageBridgeResult(result) {
|
|
9
9
|
if (
|
|
10
10
|
result != null &&
|
|
11
11
|
typeof result === 'object' &&
|
|
@@ -20,54 +20,54 @@ export function unwrapWebpageBridgeResult(result) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
|
-
* Normalizes the optional second argument to `hc.
|
|
23
|
+
* Normalizes the optional second argument to `hc.livePage(url, options)`.
|
|
24
24
|
*
|
|
25
25
|
* @param {unknown} [options] - User-provided options.
|
|
26
26
|
* @returns {{ reuse?: boolean }} Normalized open options.
|
|
27
27
|
*/
|
|
28
|
-
export function
|
|
28
|
+
export function normalizeLivePageOpenOptions(options) {
|
|
29
29
|
if (options == null) {
|
|
30
30
|
return {};
|
|
31
31
|
}
|
|
32
32
|
if (typeof options !== 'object' || Array.isArray(options)) {
|
|
33
|
-
throw new Error('hc.
|
|
33
|
+
throw new Error('hc.livePage options must be an object');
|
|
34
34
|
}
|
|
35
35
|
const raw = /** @type {Record<string, unknown>} */ (options);
|
|
36
36
|
if (!('reuse' in raw) || raw.reuse === undefined) {
|
|
37
37
|
return {};
|
|
38
38
|
}
|
|
39
39
|
if (typeof raw.reuse !== 'boolean') {
|
|
40
|
-
throw new Error('hc.
|
|
40
|
+
throw new Error('hc.livePage options.reuse must be a boolean');
|
|
41
41
|
}
|
|
42
42
|
return { reuse: raw.reuse };
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
* Normalizes optional `hc.
|
|
46
|
+
* Normalizes optional `hc.livePage().screenshot` options.
|
|
47
47
|
*
|
|
48
48
|
* @param {unknown} [options] - User-provided options (`fullPage` optional).
|
|
49
49
|
* @returns {{ fullPage: boolean }} Normalized options (default `fullPage: false`).
|
|
50
50
|
* @throws {Error} When options is present but not a plain object, or `fullPage` is not a boolean.
|
|
51
51
|
*/
|
|
52
|
-
export function
|
|
52
|
+
export function normalizeLivePageScreenshotOptions(options) {
|
|
53
53
|
if (options == null) {
|
|
54
54
|
return { fullPage: false };
|
|
55
55
|
}
|
|
56
56
|
if (typeof options !== 'object' || Array.isArray(options)) {
|
|
57
|
-
throw new Error('hc.
|
|
57
|
+
throw new Error('hc.livePage().screenshot options must be an object');
|
|
58
58
|
}
|
|
59
59
|
const raw = /** @type {Record<string, unknown>} */ (options);
|
|
60
60
|
if (!('fullPage' in raw) || raw.fullPage === undefined) {
|
|
61
61
|
return { fullPage: false };
|
|
62
62
|
}
|
|
63
63
|
if (typeof raw.fullPage !== 'boolean') {
|
|
64
|
-
throw new Error('hc.
|
|
64
|
+
throw new Error('hc.livePage().screenshot options.fullPage must be a boolean');
|
|
65
65
|
}
|
|
66
66
|
return { fullPage: raw.fullPage };
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
|
-
* Builds a
|
|
70
|
+
* Builds a live-page handle whose methods call the host live page bridge.
|
|
71
71
|
*
|
|
72
72
|
* @param {{
|
|
73
73
|
* tabId: string;
|
|
@@ -76,14 +76,15 @@ export function normalizeWebpageScreenshotOptions(options) {
|
|
|
76
76
|
* canGoBack?: boolean;
|
|
77
77
|
* canGoForward?: boolean;
|
|
78
78
|
* }} tab - Opened tab metadata from the bridge.
|
|
79
|
-
* @param {(req: Record<string, unknown>) => Promise<unknown>}
|
|
79
|
+
* @param {(req: Record<string, unknown>) => Promise<unknown>} callLivePage - Bridge transport.
|
|
80
80
|
* @param {(path: string, pngBase64: string) => Promise<string>} [writeScreenshotBytes] - Optional
|
|
81
81
|
* writer that saves PNG base64 under an allowlisted path and returns the absolute path.
|
|
82
|
-
* @returns {import('../types').
|
|
82
|
+
* @returns {import('../types').PluginLivePageHandle} Plain-object handle for the plugin world.
|
|
83
83
|
*/
|
|
84
|
-
export function
|
|
84
|
+
export function createLivePageHandle(tab, callLivePage, writeScreenshotBytes) {
|
|
85
85
|
const tabId = tab.tabId;
|
|
86
|
-
|
|
86
|
+
/** @type {import('../types').PluginLivePageHandle} */
|
|
87
|
+
const handle = {
|
|
87
88
|
tabId,
|
|
88
89
|
url: tab.url,
|
|
89
90
|
title: tab.title,
|
|
@@ -95,7 +96,7 @@ export function createWebpageHandle(tab, callWebpage, writeScreenshotBytes) {
|
|
|
95
96
|
* @returns {Promise<void>} Resolves when the tab is focused.
|
|
96
97
|
*/
|
|
97
98
|
focus: async () => {
|
|
98
|
-
|
|
99
|
+
unwrapLivePageBridgeResult(await callLivePage({ op: 'focus', tabId }));
|
|
99
100
|
},
|
|
100
101
|
/**
|
|
101
102
|
* Closes this browser tab, honoring page leave prompts.
|
|
@@ -104,10 +105,59 @@ export function createWebpageHandle(tab, callWebpage, writeScreenshotBytes) {
|
|
|
104
105
|
*/
|
|
105
106
|
close: async () => {
|
|
106
107
|
const result = /** @type {{ closed: boolean }} */ (
|
|
107
|
-
|
|
108
|
+
unwrapLivePageBridgeResult(await callLivePage({ op: 'close', tabId }))
|
|
108
109
|
);
|
|
109
110
|
return result.closed === true;
|
|
110
111
|
},
|
|
112
|
+
/**
|
|
113
|
+
* Navigates history back one entry and waits for load.
|
|
114
|
+
*
|
|
115
|
+
* @returns {Promise<void>} Resolves when load finishes.
|
|
116
|
+
*/
|
|
117
|
+
goBack: async () => {
|
|
118
|
+
applyLivePageNavSnapshot(
|
|
119
|
+
handle,
|
|
120
|
+
unwrapLivePageBridgeResult(await callLivePage({ op: 'goBack', tabId }))
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
/**
|
|
124
|
+
* Navigates history forward one entry and waits for load.
|
|
125
|
+
*
|
|
126
|
+
* @returns {Promise<void>} Resolves when load finishes.
|
|
127
|
+
*/
|
|
128
|
+
goForward: async () => {
|
|
129
|
+
applyLivePageNavSnapshot(
|
|
130
|
+
handle,
|
|
131
|
+
unwrapLivePageBridgeResult(await callLivePage({ op: 'goForward', tabId }))
|
|
132
|
+
);
|
|
133
|
+
},
|
|
134
|
+
/**
|
|
135
|
+
* Reloads the current page and waits for load.
|
|
136
|
+
*
|
|
137
|
+
* @returns {Promise<void>} Resolves when load finishes.
|
|
138
|
+
*/
|
|
139
|
+
reload: async () => {
|
|
140
|
+
applyLivePageNavSnapshot(
|
|
141
|
+
handle,
|
|
142
|
+
unwrapLivePageBridgeResult(await callLivePage({ op: 'reload', tabId }))
|
|
143
|
+
);
|
|
144
|
+
},
|
|
145
|
+
/**
|
|
146
|
+
* Loads a URL in this tab and waits for load.
|
|
147
|
+
*
|
|
148
|
+
* @param {unknown} url - Absolute http(s) or about:blank URL.
|
|
149
|
+
* @returns {Promise<void>} Resolves when load finishes.
|
|
150
|
+
*/
|
|
151
|
+
navigate: async (url) => {
|
|
152
|
+
const urlText = String(url ?? '').trim();
|
|
153
|
+
if (!urlText) {
|
|
154
|
+
throw new Error('hc.livePage().navigate requires a non-empty url');
|
|
155
|
+
}
|
|
156
|
+
applyLivePageNavSnapshot(
|
|
157
|
+
handle,
|
|
158
|
+
unwrapLivePageBridgeResult(await callLivePage({ op: 'navigate', tabId, url: urlText }))
|
|
159
|
+
);
|
|
160
|
+
},
|
|
111
161
|
/**
|
|
112
162
|
* Captures the visible viewport (or full page) as PNG and writes it via the filesystem bridge.
|
|
113
163
|
*
|
|
@@ -116,19 +166,19 @@ export function createWebpageHandle(tab, callWebpage, writeScreenshotBytes) {
|
|
|
116
166
|
* @returns {Promise<{ path: string }>} Absolute path of the written file.
|
|
117
167
|
*/
|
|
118
168
|
screenshot: async (path, screenshotOptions) => {
|
|
119
|
-
const { fullPage } =
|
|
169
|
+
const { fullPage } = normalizeLivePageScreenshotOptions(screenshotOptions);
|
|
120
170
|
const pathText = String(path ?? '').trim();
|
|
121
171
|
if (!pathText) {
|
|
122
|
-
throw new Error('hc.
|
|
172
|
+
throw new Error('hc.livePage().screenshot requires a path');
|
|
123
173
|
}
|
|
124
174
|
if (!writeScreenshotBytes) {
|
|
125
|
-
throw new Error('hc.
|
|
175
|
+
throw new Error('hc.livePage().screenshot requires hc.fs.writeBytes');
|
|
126
176
|
}
|
|
127
177
|
const capture = /** @type {{ pngBase64?: string }} */ (
|
|
128
|
-
|
|
178
|
+
unwrapLivePageBridgeResult(await callLivePage({ op: 'screenshot', tabId, fullPage }))
|
|
129
179
|
);
|
|
130
180
|
if (!capture || typeof capture.pngBase64 !== 'string' || !capture.pngBase64) {
|
|
131
|
-
throw new Error('hc.
|
|
181
|
+
throw new Error('hc.livePage().screenshot did not return image data');
|
|
132
182
|
}
|
|
133
183
|
const absolutePath = await writeScreenshotBytes(pathText, capture.pngBase64);
|
|
134
184
|
return { path: absolutePath };
|
|
@@ -144,31 +194,33 @@ export function createWebpageHandle(tab, callWebpage, writeScreenshotBytes) {
|
|
|
144
194
|
query: async (selector, queryOptions) => {
|
|
145
195
|
const selectorText = String(selector ?? '').trim();
|
|
146
196
|
if (!selectorText) {
|
|
147
|
-
throw new Error('hc.
|
|
197
|
+
throw new Error('hc.livePage().dom.query requires a selector');
|
|
148
198
|
}
|
|
149
199
|
let all;
|
|
150
200
|
let maxElements;
|
|
151
201
|
if (queryOptions != null) {
|
|
152
202
|
if (typeof queryOptions !== 'object' || Array.isArray(queryOptions)) {
|
|
153
|
-
throw new Error('hc.
|
|
203
|
+
throw new Error('hc.livePage().dom.query options must be an object');
|
|
154
204
|
}
|
|
155
205
|
const raw = /** @type {Record<string, unknown>} */ (queryOptions);
|
|
156
206
|
if ('all' in raw && raw.all !== undefined) {
|
|
157
207
|
if (typeof raw.all !== 'boolean') {
|
|
158
|
-
throw new Error('hc.
|
|
208
|
+
throw new Error('hc.livePage().dom.query options.all must be a boolean');
|
|
159
209
|
}
|
|
160
210
|
all = raw.all;
|
|
161
211
|
}
|
|
162
212
|
if ('maxElements' in raw && raw.maxElements !== undefined) {
|
|
163
213
|
if (typeof raw.maxElements !== 'number' || !Number.isFinite(raw.maxElements)) {
|
|
164
|
-
throw new Error(
|
|
214
|
+
throw new Error(
|
|
215
|
+
'hc.livePage().dom.query options.maxElements must be a finite number'
|
|
216
|
+
);
|
|
165
217
|
}
|
|
166
218
|
maxElements = raw.maxElements;
|
|
167
219
|
}
|
|
168
220
|
}
|
|
169
221
|
return /** @type {{ selector: string; matchCount: number; elements: unknown[] }} */ (
|
|
170
|
-
|
|
171
|
-
await
|
|
222
|
+
unwrapLivePageBridgeResult(
|
|
223
|
+
await callLivePage({ op: 'query', tabId, selector: selectorText, all, maxElements })
|
|
172
224
|
)
|
|
173
225
|
);
|
|
174
226
|
},
|
|
@@ -181,11 +233,11 @@ export function createWebpageHandle(tab, callWebpage, writeScreenshotBytes) {
|
|
|
181
233
|
evaluate: async (expression) => {
|
|
182
234
|
const expressionText = String(expression ?? '').trim();
|
|
183
235
|
if (!expressionText) {
|
|
184
|
-
throw new Error('hc.
|
|
236
|
+
throw new Error('hc.livePage().dom.evaluate requires an expression');
|
|
185
237
|
}
|
|
186
238
|
const result = /** @type {{ value: unknown }} */ (
|
|
187
|
-
|
|
188
|
-
await
|
|
239
|
+
unwrapLivePageBridgeResult(
|
|
240
|
+
await callLivePage({ op: 'evaluate', tabId, expression: expressionText })
|
|
189
241
|
)
|
|
190
242
|
);
|
|
191
243
|
return result.value;
|
|
@@ -199,11 +251,11 @@ export function createWebpageHandle(tab, callWebpage, writeScreenshotBytes) {
|
|
|
199
251
|
injectScript: async (source) => {
|
|
200
252
|
const sourceText = String(source ?? '');
|
|
201
253
|
if (!sourceText.trim()) {
|
|
202
|
-
throw new Error('hc.
|
|
254
|
+
throw new Error('hc.livePage().dom.injectScript requires source');
|
|
203
255
|
}
|
|
204
256
|
const result = /** @type {{ value: unknown }} */ (
|
|
205
|
-
|
|
206
|
-
await
|
|
257
|
+
unwrapLivePageBridgeResult(
|
|
258
|
+
await callLivePage({ op: 'injectScript', tabId, source: sourceText })
|
|
207
259
|
)
|
|
208
260
|
);
|
|
209
261
|
return result.value;
|
|
@@ -217,37 +269,63 @@ export function createWebpageHandle(tab, callWebpage, writeScreenshotBytes) {
|
|
|
217
269
|
injectStylesheet: async (css) => {
|
|
218
270
|
const cssText = String(css ?? '');
|
|
219
271
|
if (!cssText.trim()) {
|
|
220
|
-
throw new Error('hc.
|
|
272
|
+
throw new Error('hc.livePage().dom.injectStylesheet requires css');
|
|
221
273
|
}
|
|
222
274
|
const result = /** @type {{ key: string }} */ (
|
|
223
|
-
|
|
224
|
-
await
|
|
275
|
+
unwrapLivePageBridgeResult(
|
|
276
|
+
await callLivePage({ op: 'injectStylesheet', tabId, css: cssText })
|
|
225
277
|
)
|
|
226
278
|
);
|
|
227
279
|
return result.key;
|
|
228
280
|
}
|
|
229
281
|
}
|
|
230
282
|
};
|
|
283
|
+
return handle;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Copies navigation fields from a bridge result onto a live-page handle.
|
|
288
|
+
*
|
|
289
|
+
* @param {import('../types').PluginLivePageHandle} handle - Mutable handle to update.
|
|
290
|
+
* @param {unknown} result - Bridge result with url/title/history flags.
|
|
291
|
+
*/
|
|
292
|
+
function applyLivePageNavSnapshot(handle, result) {
|
|
293
|
+
if (result == null || typeof result !== 'object' || Array.isArray(result)) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const snapshot = /** @type {Record<string, unknown>} */ (result);
|
|
297
|
+
if (typeof snapshot.url === 'string') {
|
|
298
|
+
handle.url = snapshot.url;
|
|
299
|
+
}
|
|
300
|
+
if (typeof snapshot.title === 'string') {
|
|
301
|
+
handle.title = snapshot.title;
|
|
302
|
+
}
|
|
303
|
+
if (typeof snapshot.canGoBack === 'boolean') {
|
|
304
|
+
handle.canGoBack = snapshot.canGoBack;
|
|
305
|
+
}
|
|
306
|
+
if (typeof snapshot.canGoForward === 'boolean') {
|
|
307
|
+
handle.canGoForward = snapshot.canGoForward;
|
|
308
|
+
}
|
|
231
309
|
}
|
|
232
310
|
|
|
233
311
|
/**
|
|
234
312
|
* Opens or reuses an embedded browser tab and returns a control handle.
|
|
235
313
|
*
|
|
236
|
-
* @param {(req: Record<string, unknown>) => Promise<unknown>}
|
|
237
|
-
* that accepts
|
|
314
|
+
* @param {(req: Record<string, unknown>) => Promise<unknown>} callLivePage - Bridge transport
|
|
315
|
+
* that accepts ScriptLivePageRequest-shaped payloads (`op` plus fields).
|
|
238
316
|
* @param {unknown} [url] - Optional URL; omit to bind the active browser tab.
|
|
239
317
|
* @param {unknown} [openOptions] - Optional `{ reuse }` (default true).
|
|
240
318
|
* @param {(path: string, pngBase64: string) => Promise<string>} [writeScreenshotBytes] - Optional
|
|
241
319
|
* writer used by `page.screenshot`.
|
|
242
|
-
* @returns {Promise<import('../types').
|
|
320
|
+
* @returns {Promise<import('../types').PluginLivePageHandle>} Live-page handle.
|
|
243
321
|
*/
|
|
244
|
-
export async function
|
|
245
|
-
const normalizedOptions =
|
|
322
|
+
export async function openLivePage(callLivePage, url, openOptions, writeScreenshotBytes) {
|
|
323
|
+
const normalizedOptions = normalizeLivePageOpenOptions(openOptions);
|
|
246
324
|
let openUrl;
|
|
247
325
|
if (url !== undefined && url !== null) {
|
|
248
326
|
const trimmed = String(url).trim();
|
|
249
327
|
if (!trimmed) {
|
|
250
|
-
throw new Error('hc.
|
|
328
|
+
throw new Error('hc.livePage requires a non-empty url when provided');
|
|
251
329
|
}
|
|
252
330
|
openUrl = trimmed;
|
|
253
331
|
}
|
|
@@ -258,8 +336,8 @@ export async function openWebpage(callWebpage, url, openOptions, writeScreenshot
|
|
|
258
336
|
canGoBack?: boolean;
|
|
259
337
|
canGoForward?: boolean;
|
|
260
338
|
}} */ (
|
|
261
|
-
|
|
262
|
-
await
|
|
339
|
+
unwrapLivePageBridgeResult(
|
|
340
|
+
await callLivePage({
|
|
263
341
|
op: 'open',
|
|
264
342
|
url: openUrl,
|
|
265
343
|
reuse: normalizedOptions.reuse
|
|
@@ -267,7 +345,7 @@ export async function openWebpage(callWebpage, url, openOptions, writeScreenshot
|
|
|
267
345
|
)
|
|
268
346
|
);
|
|
269
347
|
if (!opened || typeof opened.tabId !== 'string') {
|
|
270
|
-
throw new Error('hc.
|
|
348
|
+
throw new Error('hc.livePage open did not return a tab');
|
|
271
349
|
}
|
|
272
|
-
return
|
|
350
|
+
return createLivePageHandle(opened, callLivePage, writeScreenshotBytes);
|
|
273
351
|
}
|
package/dist/snippets.d.ts
CHANGED
|
@@ -271,9 +271,9 @@ interface HcSendRequestResponse {
|
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
/**
|
|
274
|
-
* Live DOM helpers on a
|
|
274
|
+
* Live DOM helpers on a live-page handle from hc.livePage.
|
|
275
275
|
*/
|
|
276
|
-
interface
|
|
276
|
+
interface HcLivePageDom {
|
|
277
277
|
/**
|
|
278
278
|
* Queries the live page DOM with a CSS selector.
|
|
279
279
|
*/
|
|
@@ -299,15 +299,19 @@ interface HcWebpageDom {
|
|
|
299
299
|
}
|
|
300
300
|
|
|
301
301
|
/**
|
|
302
|
-
* Handle returned by hc.
|
|
302
|
+
* Handle returned by hc.livePage for an embedded browser tab.
|
|
303
303
|
*/
|
|
304
|
-
interface
|
|
304
|
+
interface HcLivePageHandle {
|
|
305
305
|
readonly tabId: string;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
306
|
+
/** Current page URL. Updated after navigate / goBack / goForward / reload. */
|
|
307
|
+
url: string;
|
|
308
|
+
/** Document title. Updated after navigate / goBack / goForward / reload. */
|
|
309
|
+
title: string;
|
|
310
|
+
/** Whether history can go back. Updated after navigation helpers. */
|
|
311
|
+
canGoBack: boolean;
|
|
312
|
+
/** Whether history can go forward. Updated after navigation helpers. */
|
|
313
|
+
canGoForward: boolean;
|
|
314
|
+
readonly dom: HcLivePageDom;
|
|
311
315
|
/**
|
|
312
316
|
* Focuses this browser tab in the tab bar.
|
|
313
317
|
*/
|
|
@@ -316,6 +320,24 @@ interface HcWebpageHandle {
|
|
|
316
320
|
* Closes this browser tab. Returns false when the user cancels a leave prompt.
|
|
317
321
|
*/
|
|
318
322
|
close(): Promise<boolean>;
|
|
323
|
+
/**
|
|
324
|
+
* Navigates history back one entry and waits for load. Updates url/title/history flags.
|
|
325
|
+
*/
|
|
326
|
+
goBack(): Promise<void>;
|
|
327
|
+
/**
|
|
328
|
+
* Navigates history forward one entry and waits for load. Updates url/title/history flags.
|
|
329
|
+
*/
|
|
330
|
+
goForward(): Promise<void>;
|
|
331
|
+
/**
|
|
332
|
+
* Reloads the current page and waits for load. Updates url/title/history flags.
|
|
333
|
+
*/
|
|
334
|
+
reload(): Promise<void>;
|
|
335
|
+
/**
|
|
336
|
+
* Loads a URL in this tab and waits for load. Updates url/title/history flags.
|
|
337
|
+
*
|
|
338
|
+
* @param url - Absolute http(s) or about:blank URL.
|
|
339
|
+
*/
|
|
340
|
+
navigate(url: string): Promise<void>;
|
|
319
341
|
/**
|
|
320
342
|
* Captures the visible viewport as PNG and writes it under the script file access root.
|
|
321
343
|
*
|
|
@@ -465,13 +487,13 @@ interface HcScriptApi {
|
|
|
465
487
|
ask(prompt: string, options?: HcAskOptions): Promise<string | null>;
|
|
466
488
|
/**
|
|
467
489
|
* Opens or reuses an embedded browser tab and returns a control handle.
|
|
468
|
-
* Requires Settings → General → Allow script
|
|
490
|
+
* Requires Settings → General → Allow script live page access.
|
|
469
491
|
*
|
|
470
492
|
* @param url - Optional URL to open or reuse; omit to bind the active browser tab.
|
|
471
493
|
* @param options - Optional `{ reuse }` (default true).
|
|
472
|
-
* @throws When
|
|
494
|
+
* @throws When live-page access is disabled or unavailable in this context.
|
|
473
495
|
*/
|
|
474
|
-
|
|
496
|
+
livePage(url?: string, options?: { reuse?: boolean }): Promise<HcLivePageHandle>;
|
|
475
497
|
/**
|
|
476
498
|
* Resolves after the given delay. Use for pacing between script steps.
|
|
477
499
|
*
|