@harborclient/sdk 1.3.8 → 1.4.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 +9 -0
- package/dist/components/AnchorMenuPanel/index.d.ts.map +1 -1
- package/dist/components/AnchorMenuPanel/index.js +13 -5
- package/dist/components/Autocomplete/useAutocomplete.d.ts +14 -1
- package/dist/components/Autocomplete/useAutocomplete.d.ts.map +1 -1
- package/dist/components/Autocomplete/useAutocomplete.js +28 -1
- package/dist/components/RoundButton/index.d.ts +5 -1
- package/dist/components/RoundButton/index.d.ts.map +1 -1
- package/dist/components/RoundButton/index.js +2 -2
- package/dist/components/RowActionsMenu/index.d.ts.map +1 -1
- package/dist/components/RowActionsMenu/index.js +22 -11
- package/dist/components/Sidebar/index.d.ts +4 -1
- package/dist/components/Sidebar/index.d.ts.map +1 -1
- package/dist/components/Sidebar/index.js +4 -2
- package/dist/components/SidebarItem/sidebarItemClasses.d.ts +3 -2
- package/dist/components/SidebarItem/sidebarItemClasses.d.ts.map +1 -1
- package/dist/components/SidebarItem/sidebarItemClasses.js +3 -2
- package/dist/components/SidebarRail/SidebarRailItem.d.ts +6 -1
- package/dist/components/SidebarRail/SidebarRailItem.d.ts.map +1 -1
- package/dist/components/SidebarRail/SidebarRailItem.js +25 -5
- package/dist/components/SidebarRail/SidebarRailSeparator.d.ts +3 -3
- package/dist/components/SidebarRail/SidebarRailSeparator.js +3 -3
- package/dist/components/SidebarSection/SectionItem.js +1 -1
- package/dist/components/TabBar/index.d.ts.map +1 -1
- package/dist/components/TabBar/index.js +1 -1
- package/dist/components/VariableInput/index.d.ts +14 -1
- package/dist/components/VariableInput/index.d.ts.map +1 -1
- package/dist/components/VariableInput/index.js +4 -2
- package/dist/components/rowActionsMenuHelpers.d.ts +4 -0
- package/dist/components/rowActionsMenuHelpers.d.ts.map +1 -1
- package/dist/components/rowActionsMenuHelpers.js +6 -1
- package/dist/runtime/createBridgedPluginContext.js +156 -0
- package/dist/runtime/reactHost.d.ts +32 -0
- package/dist/runtime/webpageHandle.d.ts +50 -0
- package/dist/runtime/webpageHandle.js +273 -0
- package/dist/snippets.d.ts +67 -0
- package/dist/types.d.ts +506 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -33,6 +33,10 @@ export function findAdjacentEnabledIndex(items, fromIndex, direction) {
|
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
35
|
* Returns the first or last enabled flat item index.
|
|
36
|
+
*
|
|
37
|
+
* @param items - Flat menu items to search.
|
|
38
|
+
* @param fromEnd - When true, returns the last enabled index; otherwise the first.
|
|
39
|
+
* @returns Enabled index, or `null` when every item is disabled / the list is empty.
|
|
36
40
|
*/
|
|
37
41
|
export function findEdgeEnabledIndex(items, fromEnd) {
|
|
38
42
|
if (fromEnd) {
|
|
@@ -43,7 +47,8 @@ export function findEdgeEnabledIndex(items, fromEnd) {
|
|
|
43
47
|
}
|
|
44
48
|
return null;
|
|
45
49
|
}
|
|
46
|
-
|
|
50
|
+
const index = items.findIndex((item) => isMenuItemEnabled(item));
|
|
51
|
+
return index >= 0 ? index : null;
|
|
47
52
|
}
|
|
48
53
|
/**
|
|
49
54
|
* Builds an optional reorder group for move-up/move-down row actions.
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { bridgeInvoke, bridgeOn } from './hcBridge.js';
|
|
8
8
|
import { createPluginDatabaseApi } from './pluginDatabaseApi.js';
|
|
9
9
|
import { setHostReact } from './reactHost.js';
|
|
10
|
+
import { openWebpage } from './webpageHandle.js';
|
|
10
11
|
|
|
11
12
|
/** @type {Map<string, Set<(...args: unknown[]) => void | Promise<void>>>} */
|
|
12
13
|
const commandHandlers = new Map();
|
|
@@ -352,6 +353,57 @@ export function createBridgedPluginContext({ pluginId, mode, contributionId, rea
|
|
|
352
353
|
*/
|
|
353
354
|
const assertAi = () => assertPermission('ai');
|
|
354
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Asserts browser permission for embedded webpage control.
|
|
358
|
+
*/
|
|
359
|
+
const assertBrowser = () => assertPermission('browser');
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Asserts live-server permission for Harbor Live Server APIs.
|
|
363
|
+
*/
|
|
364
|
+
const assertLiveServer = () => assertPermission('live-server');
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Invokes a webpage session op on the host renderer via the plugin bridge.
|
|
368
|
+
*
|
|
369
|
+
* @param {Record<string, unknown>} req - ScriptWebpageRequest-shaped payload.
|
|
370
|
+
* @returns {Promise<unknown>} Host session result.
|
|
371
|
+
*/
|
|
372
|
+
const callWebpage = async (req) => {
|
|
373
|
+
const op = String(req.op ?? '');
|
|
374
|
+
switch (op) {
|
|
375
|
+
case 'open':
|
|
376
|
+
return bridgeInvoke('webpage.open', { url: req.url, reuse: req.reuse });
|
|
377
|
+
case 'focus':
|
|
378
|
+
return bridgeInvoke('webpage.focus', { tabId: req.tabId });
|
|
379
|
+
case 'close':
|
|
380
|
+
return bridgeInvoke('webpage.close', { tabId: req.tabId });
|
|
381
|
+
case 'query':
|
|
382
|
+
return bridgeInvoke('webpage.query', {
|
|
383
|
+
tabId: req.tabId,
|
|
384
|
+
selector: req.selector,
|
|
385
|
+
all: req.all,
|
|
386
|
+
maxElements: req.maxElements
|
|
387
|
+
});
|
|
388
|
+
case 'evaluate':
|
|
389
|
+
return bridgeInvoke('webpage.evaluate', {
|
|
390
|
+
tabId: req.tabId,
|
|
391
|
+
expression: req.expression
|
|
392
|
+
});
|
|
393
|
+
case 'injectScript':
|
|
394
|
+
return bridgeInvoke('webpage.injectScript', { tabId: req.tabId, source: req.source });
|
|
395
|
+
case 'injectStylesheet':
|
|
396
|
+
return bridgeInvoke('webpage.injectStylesheet', { tabId: req.tabId, css: req.css });
|
|
397
|
+
case 'screenshot':
|
|
398
|
+
return bridgeInvoke('webpage.screenshot', {
|
|
399
|
+
tabId: req.tabId,
|
|
400
|
+
fullPage: req.fullPage === true
|
|
401
|
+
});
|
|
402
|
+
default:
|
|
403
|
+
throw new Error(`Unsupported webpage bridge op: ${op}`);
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
355
407
|
/**
|
|
356
408
|
* Asserts that a contribution id is declared in manifest.contributes.
|
|
357
409
|
*
|
|
@@ -482,6 +534,18 @@ export function createBridgedPluginContext({ pluginId, mode, contributionId, rea
|
|
|
482
534
|
assertPermission('filesystem:write');
|
|
483
535
|
await bridgeInvoke('fs.writeFile', { path, content });
|
|
484
536
|
},
|
|
537
|
+
writeBytes: async (path, bytes) => {
|
|
538
|
+
assertPermission('filesystem:write');
|
|
539
|
+
const u8 =
|
|
540
|
+
bytes instanceof Uint8Array
|
|
541
|
+
? bytes
|
|
542
|
+
: new Uint8Array(/** @type {ArrayLike<number>} */ (bytes));
|
|
543
|
+
let binary = '';
|
|
544
|
+
for (let i = 0; i < u8.length; i += 1) {
|
|
545
|
+
binary += String.fromCharCode(u8[i] ?? 0);
|
|
546
|
+
}
|
|
547
|
+
return bridgeInvoke('fs.writeBytes', { path, base64: btoa(binary) });
|
|
548
|
+
},
|
|
485
549
|
watchFile: (path, listener) => {
|
|
486
550
|
assertPermission('filesystem:read');
|
|
487
551
|
const unsubscribe = bridgeOn(`fs.watch:${path}`, () => {
|
|
@@ -1213,6 +1277,70 @@ export function createBridgedPluginContext({ pluginId, mode, contributionId, rea
|
|
|
1213
1277
|
});
|
|
1214
1278
|
}
|
|
1215
1279
|
},
|
|
1280
|
+
liveServers: {
|
|
1281
|
+
list: async () => {
|
|
1282
|
+
assertLiveServer();
|
|
1283
|
+
return bridgeInvoke('liveServers.list');
|
|
1284
|
+
},
|
|
1285
|
+
get: async (idOrUuid) => {
|
|
1286
|
+
assertLiveServer();
|
|
1287
|
+
return bridgeInvoke('liveServers.get', { idOrUuid });
|
|
1288
|
+
},
|
|
1289
|
+
create: async (input) => {
|
|
1290
|
+
assertLiveServer();
|
|
1291
|
+
return bridgeInvoke('liveServers.create', { input });
|
|
1292
|
+
},
|
|
1293
|
+
update: async (input) => {
|
|
1294
|
+
assertLiveServer();
|
|
1295
|
+
return bridgeInvoke('liveServers.update', { input });
|
|
1296
|
+
},
|
|
1297
|
+
delete: async (id) => {
|
|
1298
|
+
assertLiveServer();
|
|
1299
|
+
await bridgeInvoke('liveServers.delete', { id });
|
|
1300
|
+
},
|
|
1301
|
+
start: async (input) => {
|
|
1302
|
+
assertLiveServer();
|
|
1303
|
+
return bridgeInvoke('liveServers.start', { input });
|
|
1304
|
+
},
|
|
1305
|
+
stop: async (query) => {
|
|
1306
|
+
assertLiveServer();
|
|
1307
|
+
await bridgeInvoke('liveServers.stop', { query });
|
|
1308
|
+
},
|
|
1309
|
+
listRunning: async () => {
|
|
1310
|
+
assertLiveServer();
|
|
1311
|
+
return bridgeInvoke('liveServers.listRunning');
|
|
1312
|
+
},
|
|
1313
|
+
getStatus: async (query) => {
|
|
1314
|
+
assertLiveServer();
|
|
1315
|
+
return bridgeInvoke('liveServers.getStatus', { query });
|
|
1316
|
+
},
|
|
1317
|
+
getLogs: async (query) => {
|
|
1318
|
+
assertLiveServer();
|
|
1319
|
+
return bridgeInvoke('liveServers.getLogs', { query });
|
|
1320
|
+
},
|
|
1321
|
+
clearLogs: async (query) => {
|
|
1322
|
+
assertLiveServer();
|
|
1323
|
+
await bridgeInvoke('liveServers.clearLogs', { query });
|
|
1324
|
+
},
|
|
1325
|
+
onRunningChanged: (listener) => {
|
|
1326
|
+
assertLiveServer();
|
|
1327
|
+
const unsubscribe = bridgeOn('liveServers.runningChanged', listener);
|
|
1328
|
+
return track({
|
|
1329
|
+
dispose: () => {
|
|
1330
|
+
unsubscribe();
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
},
|
|
1334
|
+
onRequestLog: (listener) => {
|
|
1335
|
+
assertLiveServer();
|
|
1336
|
+
const unsubscribe = bridgeOn('liveServers.requestLog', listener);
|
|
1337
|
+
return track({
|
|
1338
|
+
dispose: () => {
|
|
1339
|
+
unsubscribe();
|
|
1340
|
+
}
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
},
|
|
1216
1344
|
ai: {
|
|
1217
1345
|
registerChatPointer: (config) => {
|
|
1218
1346
|
assertAi();
|
|
@@ -1243,6 +1371,34 @@ export function createBridgedPluginContext({ pluginId, mode, contributionId, rea
|
|
|
1243
1371
|
selection: input?.selection
|
|
1244
1372
|
});
|
|
1245
1373
|
}
|
|
1374
|
+
},
|
|
1375
|
+
/**
|
|
1376
|
+
* Opens or reuses an embedded browser tab and returns a control handle.
|
|
1377
|
+
*
|
|
1378
|
+
* Requires the `browser` permission. Same semantics as request-script `hc.webpage`.
|
|
1379
|
+
*
|
|
1380
|
+
* @param {string} [url] - Optional URL; omit to bind the active browser tab.
|
|
1381
|
+
* @param {{ reuse?: boolean }} [options] - Optional `{ reuse }` (default true).
|
|
1382
|
+
* @returns {Promise<import('../types').PluginWebpageHandle>} Webpage handle.
|
|
1383
|
+
*/
|
|
1384
|
+
webpage: async (url, options) => {
|
|
1385
|
+
assertBrowser();
|
|
1386
|
+
/**
|
|
1387
|
+
* Writes screenshot PNG bytes via the plugin filesystem bridge.
|
|
1388
|
+
*
|
|
1389
|
+
* @param {string} path - Relative or absolute allowlisted path.
|
|
1390
|
+
* @param {string} pngBase64 - Base64-encoded PNG payload.
|
|
1391
|
+
* @returns {Promise<string>} Absolute written path.
|
|
1392
|
+
*/
|
|
1393
|
+
const writeScreenshotBytes = async (path, pngBase64) => {
|
|
1394
|
+
assertPermission('filesystem:write');
|
|
1395
|
+
const result = await bridgeInvoke('fs.writeBytes', { path, base64: pngBase64 });
|
|
1396
|
+
if (typeof result !== 'string' || !result.trim()) {
|
|
1397
|
+
throw new Error('hc.webpage().screenshot failed to resolve write path');
|
|
1398
|
+
}
|
|
1399
|
+
return result;
|
|
1400
|
+
};
|
|
1401
|
+
return openWebpage(callWebpage, url, options, writeScreenshotBytes);
|
|
1246
1402
|
}
|
|
1247
1403
|
};
|
|
1248
1404
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type * as React from 'react';
|
|
2
|
+
import type * as ReactDOM from 'react-dom';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Installs the HarborClient renderer React instance for plugin JSX and hooks.
|
|
6
|
+
*
|
|
7
|
+
* @param react - React namespace from the host.
|
|
8
|
+
*/
|
|
9
|
+
export function setHostReact(react: typeof React): void;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Returns the installed host React instance.
|
|
13
|
+
*
|
|
14
|
+
* @returns Host React namespace.
|
|
15
|
+
* @throws When {@link setHostReact} has not been called yet.
|
|
16
|
+
*/
|
|
17
|
+
export function requireHostReact(): typeof React;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Installs the HarborClient renderer React DOM instance for plugin portals.
|
|
21
|
+
*
|
|
22
|
+
* @param reactDom - React DOM namespace from the host shim.
|
|
23
|
+
*/
|
|
24
|
+
export function setHostReactDom(reactDom: typeof ReactDOM): void;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Returns the installed host React DOM instance.
|
|
28
|
+
*
|
|
29
|
+
* @returns Host React DOM namespace.
|
|
30
|
+
* @throws When {@link setHostReactDom} has not been called yet.
|
|
31
|
+
*/
|
|
32
|
+
export function requireHostReactDom(): typeof ReactDOM;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { PluginWebpageHandle } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Throws when a webpage 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 unwrapWebpageBridgeResult(result: unknown): unknown;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Normalizes the optional second argument to `hc.webpage(url, options)`.
|
|
14
|
+
*
|
|
15
|
+
* @param options - User-provided options.
|
|
16
|
+
* @returns Normalized open options.
|
|
17
|
+
*/
|
|
18
|
+
export function normalizeWebpageOpenOptions(options?: unknown): { reuse?: boolean };
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Builds a webpage handle whose methods call the host webpage bridge.
|
|
22
|
+
*
|
|
23
|
+
* @param tab - Opened tab metadata from the bridge.
|
|
24
|
+
* @param callWebpage - Bridge transport.
|
|
25
|
+
* @returns Plain-object handle for the plugin world.
|
|
26
|
+
*/
|
|
27
|
+
export function createWebpageHandle(
|
|
28
|
+
tab: {
|
|
29
|
+
tabId: string;
|
|
30
|
+
url: string;
|
|
31
|
+
title: string;
|
|
32
|
+
canGoBack?: boolean;
|
|
33
|
+
canGoForward?: boolean;
|
|
34
|
+
},
|
|
35
|
+
callWebpage: (req: Record<string, unknown>) => Promise<unknown>
|
|
36
|
+
): PluginWebpageHandle;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Opens or reuses an embedded browser tab and returns a control handle.
|
|
40
|
+
*
|
|
41
|
+
* @param callWebpage - Bridge transport that accepts ScriptWebpageRequest-shaped payloads.
|
|
42
|
+
* @param url - Optional URL; omit to bind the active browser tab.
|
|
43
|
+
* @param openOptions - Optional `{ reuse }` (default true).
|
|
44
|
+
* @returns Webpage handle.
|
|
45
|
+
*/
|
|
46
|
+
export function openWebpage(
|
|
47
|
+
callWebpage: (req: Record<string, unknown>) => Promise<unknown>,
|
|
48
|
+
url?: unknown,
|
|
49
|
+
openOptions?: unknown
|
|
50
|
+
): Promise<PluginWebpageHandle>;
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Throws when a webpage bridge result is an `{ error }` object.
|
|
3
|
+
*
|
|
4
|
+
* @param {unknown} result - Raw bridge result.
|
|
5
|
+
* @returns {unknown} The result when it is not an error.
|
|
6
|
+
* @throws {Error} When the bridge returned `{ error: string }`.
|
|
7
|
+
*/
|
|
8
|
+
export function unwrapWebpageBridgeResult(result) {
|
|
9
|
+
if (
|
|
10
|
+
result != null &&
|
|
11
|
+
typeof result === 'object' &&
|
|
12
|
+
!Array.isArray(result) &&
|
|
13
|
+
Object.keys(result).length === 1 &&
|
|
14
|
+
'error' in result &&
|
|
15
|
+
typeof (/** @type {{ error: unknown }} */ (result).error) === 'string'
|
|
16
|
+
) {
|
|
17
|
+
throw new Error(/** @type {{ error: string }} */ (result).error);
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Normalizes the optional second argument to `hc.webpage(url, options)`.
|
|
24
|
+
*
|
|
25
|
+
* @param {unknown} [options] - User-provided options.
|
|
26
|
+
* @returns {{ reuse?: boolean }} Normalized open options.
|
|
27
|
+
*/
|
|
28
|
+
export function normalizeWebpageOpenOptions(options) {
|
|
29
|
+
if (options == null) {
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
if (typeof options !== 'object' || Array.isArray(options)) {
|
|
33
|
+
throw new Error('hc.webpage options must be an object');
|
|
34
|
+
}
|
|
35
|
+
const raw = /** @type {Record<string, unknown>} */ (options);
|
|
36
|
+
if (!('reuse' in raw) || raw.reuse === undefined) {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
if (typeof raw.reuse !== 'boolean') {
|
|
40
|
+
throw new Error('hc.webpage options.reuse must be a boolean');
|
|
41
|
+
}
|
|
42
|
+
return { reuse: raw.reuse };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Normalizes optional `hc.webpage().screenshot` options.
|
|
47
|
+
*
|
|
48
|
+
* @param {unknown} [options] - User-provided options (`fullPage` optional).
|
|
49
|
+
* @returns {{ fullPage: boolean }} Normalized options (default `fullPage: false`).
|
|
50
|
+
* @throws {Error} When options is present but not a plain object, or `fullPage` is not a boolean.
|
|
51
|
+
*/
|
|
52
|
+
export function normalizeWebpageScreenshotOptions(options) {
|
|
53
|
+
if (options == null) {
|
|
54
|
+
return { fullPage: false };
|
|
55
|
+
}
|
|
56
|
+
if (typeof options !== 'object' || Array.isArray(options)) {
|
|
57
|
+
throw new Error('hc.webpage().screenshot options must be an object');
|
|
58
|
+
}
|
|
59
|
+
const raw = /** @type {Record<string, unknown>} */ (options);
|
|
60
|
+
if (!('fullPage' in raw) || raw.fullPage === undefined) {
|
|
61
|
+
return { fullPage: false };
|
|
62
|
+
}
|
|
63
|
+
if (typeof raw.fullPage !== 'boolean') {
|
|
64
|
+
throw new Error('hc.webpage().screenshot options.fullPage must be a boolean');
|
|
65
|
+
}
|
|
66
|
+
return { fullPage: raw.fullPage };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Builds a webpage handle whose methods call the host webpage bridge.
|
|
71
|
+
*
|
|
72
|
+
* @param {{
|
|
73
|
+
* tabId: string;
|
|
74
|
+
* url: string;
|
|
75
|
+
* title: string;
|
|
76
|
+
* canGoBack?: boolean;
|
|
77
|
+
* canGoForward?: boolean;
|
|
78
|
+
* }} tab - Opened tab metadata from the bridge.
|
|
79
|
+
* @param {(req: Record<string, unknown>) => Promise<unknown>} callWebpage - Bridge transport.
|
|
80
|
+
* @param {(path: string, pngBase64: string) => Promise<string>} [writeScreenshotBytes] - Optional
|
|
81
|
+
* writer that saves PNG base64 under an allowlisted path and returns the absolute path.
|
|
82
|
+
* @returns {import('../types').PluginWebpageHandle} Plain-object handle for the plugin world.
|
|
83
|
+
*/
|
|
84
|
+
export function createWebpageHandle(tab, callWebpage, writeScreenshotBytes) {
|
|
85
|
+
const tabId = tab.tabId;
|
|
86
|
+
return {
|
|
87
|
+
tabId,
|
|
88
|
+
url: tab.url,
|
|
89
|
+
title: tab.title,
|
|
90
|
+
canGoBack: tab.canGoBack === true,
|
|
91
|
+
canGoForward: tab.canGoForward === true,
|
|
92
|
+
/**
|
|
93
|
+
* Focuses this browser tab in the HarborClient tab bar.
|
|
94
|
+
*
|
|
95
|
+
* @returns {Promise<void>} Resolves when the tab is focused.
|
|
96
|
+
*/
|
|
97
|
+
focus: async () => {
|
|
98
|
+
unwrapWebpageBridgeResult(await callWebpage({ op: 'focus', tabId }));
|
|
99
|
+
},
|
|
100
|
+
/**
|
|
101
|
+
* Closes this browser tab, honoring page leave prompts.
|
|
102
|
+
*
|
|
103
|
+
* @returns {Promise<boolean>} True when closed; false when the user chose to stay.
|
|
104
|
+
*/
|
|
105
|
+
close: async () => {
|
|
106
|
+
const result = /** @type {{ closed: boolean }} */ (
|
|
107
|
+
unwrapWebpageBridgeResult(await callWebpage({ op: 'close', tabId }))
|
|
108
|
+
);
|
|
109
|
+
return result.closed === true;
|
|
110
|
+
},
|
|
111
|
+
/**
|
|
112
|
+
* Captures the visible viewport (or full page) as PNG and writes it via the filesystem bridge.
|
|
113
|
+
*
|
|
114
|
+
* @param {unknown} path - Relative (plugin root) or absolute allowlisted path.
|
|
115
|
+
* @param {unknown} [screenshotOptions] - Optional `{ fullPage }` (default false).
|
|
116
|
+
* @returns {Promise<{ path: string }>} Absolute path of the written file.
|
|
117
|
+
*/
|
|
118
|
+
screenshot: async (path, screenshotOptions) => {
|
|
119
|
+
const { fullPage } = normalizeWebpageScreenshotOptions(screenshotOptions);
|
|
120
|
+
const pathText = String(path ?? '').trim();
|
|
121
|
+
if (!pathText) {
|
|
122
|
+
throw new Error('hc.webpage().screenshot requires a path');
|
|
123
|
+
}
|
|
124
|
+
if (!writeScreenshotBytes) {
|
|
125
|
+
throw new Error('hc.webpage().screenshot requires hc.fs.writeBytes');
|
|
126
|
+
}
|
|
127
|
+
const capture = /** @type {{ pngBase64?: string }} */ (
|
|
128
|
+
unwrapWebpageBridgeResult(await callWebpage({ op: 'screenshot', tabId, fullPage }))
|
|
129
|
+
);
|
|
130
|
+
if (!capture || typeof capture.pngBase64 !== 'string' || !capture.pngBase64) {
|
|
131
|
+
throw new Error('hc.webpage().screenshot did not return image data');
|
|
132
|
+
}
|
|
133
|
+
const absolutePath = await writeScreenshotBytes(pathText, capture.pngBase64);
|
|
134
|
+
return { path: absolutePath };
|
|
135
|
+
},
|
|
136
|
+
dom: {
|
|
137
|
+
/**
|
|
138
|
+
* Queries the live page DOM with a CSS selector.
|
|
139
|
+
*
|
|
140
|
+
* @param {string} selector - CSS selector.
|
|
141
|
+
* @param {{ all?: boolean; maxElements?: number }} [queryOptions] - Optional query flags.
|
|
142
|
+
* @returns {Promise<{ selector: string; matchCount: number; elements: unknown[] }>}
|
|
143
|
+
*/
|
|
144
|
+
query: async (selector, queryOptions) => {
|
|
145
|
+
const selectorText = String(selector ?? '').trim();
|
|
146
|
+
if (!selectorText) {
|
|
147
|
+
throw new Error('hc.webpage().dom.query requires a selector');
|
|
148
|
+
}
|
|
149
|
+
let all;
|
|
150
|
+
let maxElements;
|
|
151
|
+
if (queryOptions != null) {
|
|
152
|
+
if (typeof queryOptions !== 'object' || Array.isArray(queryOptions)) {
|
|
153
|
+
throw new Error('hc.webpage().dom.query options must be an object');
|
|
154
|
+
}
|
|
155
|
+
const raw = /** @type {Record<string, unknown>} */ (queryOptions);
|
|
156
|
+
if ('all' in raw && raw.all !== undefined) {
|
|
157
|
+
if (typeof raw.all !== 'boolean') {
|
|
158
|
+
throw new Error('hc.webpage().dom.query options.all must be a boolean');
|
|
159
|
+
}
|
|
160
|
+
all = raw.all;
|
|
161
|
+
}
|
|
162
|
+
if ('maxElements' in raw && raw.maxElements !== undefined) {
|
|
163
|
+
if (typeof raw.maxElements !== 'number' || !Number.isFinite(raw.maxElements)) {
|
|
164
|
+
throw new Error('hc.webpage().dom.query options.maxElements must be a finite number');
|
|
165
|
+
}
|
|
166
|
+
maxElements = raw.maxElements;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return /** @type {{ selector: string; matchCount: number; elements: unknown[] }} */ (
|
|
170
|
+
unwrapWebpageBridgeResult(
|
|
171
|
+
await callWebpage({ op: 'query', tabId, selector: selectorText, all, maxElements })
|
|
172
|
+
)
|
|
173
|
+
);
|
|
174
|
+
},
|
|
175
|
+
/**
|
|
176
|
+
* Evaluates JavaScript in the page main world and returns the result.
|
|
177
|
+
*
|
|
178
|
+
* @param {string} expression - JavaScript source that returns a JSON-serializable value.
|
|
179
|
+
* @returns {Promise<unknown>} Evaluation result.
|
|
180
|
+
*/
|
|
181
|
+
evaluate: async (expression) => {
|
|
182
|
+
const expressionText = String(expression ?? '').trim();
|
|
183
|
+
if (!expressionText) {
|
|
184
|
+
throw new Error('hc.webpage().dom.evaluate requires an expression');
|
|
185
|
+
}
|
|
186
|
+
const result = /** @type {{ value: unknown }} */ (
|
|
187
|
+
unwrapWebpageBridgeResult(
|
|
188
|
+
await callWebpage({ op: 'evaluate', tabId, expression: expressionText })
|
|
189
|
+
)
|
|
190
|
+
);
|
|
191
|
+
return result.value;
|
|
192
|
+
},
|
|
193
|
+
/**
|
|
194
|
+
* Injects and runs JavaScript source in the page main world.
|
|
195
|
+
*
|
|
196
|
+
* @param {string} source - JavaScript source to inject.
|
|
197
|
+
* @returns {Promise<unknown>} Evaluation result from the injected script.
|
|
198
|
+
*/
|
|
199
|
+
injectScript: async (source) => {
|
|
200
|
+
const sourceText = String(source ?? '');
|
|
201
|
+
if (!sourceText.trim()) {
|
|
202
|
+
throw new Error('hc.webpage().dom.injectScript requires source');
|
|
203
|
+
}
|
|
204
|
+
const result = /** @type {{ value: unknown }} */ (
|
|
205
|
+
unwrapWebpageBridgeResult(
|
|
206
|
+
await callWebpage({ op: 'injectScript', tabId, source: sourceText })
|
|
207
|
+
)
|
|
208
|
+
);
|
|
209
|
+
return result.value;
|
|
210
|
+
},
|
|
211
|
+
/**
|
|
212
|
+
* Injects a CSS stylesheet into the page.
|
|
213
|
+
*
|
|
214
|
+
* @param {string} css - Stylesheet source.
|
|
215
|
+
* @returns {Promise<string>} Electron insertion key.
|
|
216
|
+
*/
|
|
217
|
+
injectStylesheet: async (css) => {
|
|
218
|
+
const cssText = String(css ?? '');
|
|
219
|
+
if (!cssText.trim()) {
|
|
220
|
+
throw new Error('hc.webpage().dom.injectStylesheet requires css');
|
|
221
|
+
}
|
|
222
|
+
const result = /** @type {{ key: string }} */ (
|
|
223
|
+
unwrapWebpageBridgeResult(
|
|
224
|
+
await callWebpage({ op: 'injectStylesheet', tabId, css: cssText })
|
|
225
|
+
)
|
|
226
|
+
);
|
|
227
|
+
return result.key;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Opens or reuses an embedded browser tab and returns a control handle.
|
|
235
|
+
*
|
|
236
|
+
* @param {(req: Record<string, unknown>) => Promise<unknown>} callWebpage - Bridge transport
|
|
237
|
+
* that accepts ScriptWebpageRequest-shaped payloads (`op` plus fields).
|
|
238
|
+
* @param {unknown} [url] - Optional URL; omit to bind the active browser tab.
|
|
239
|
+
* @param {unknown} [openOptions] - Optional `{ reuse }` (default true).
|
|
240
|
+
* @param {(path: string, pngBase64: string) => Promise<string>} [writeScreenshotBytes] - Optional
|
|
241
|
+
* writer used by `page.screenshot`.
|
|
242
|
+
* @returns {Promise<import('../types').PluginWebpageHandle>} Webpage handle.
|
|
243
|
+
*/
|
|
244
|
+
export async function openWebpage(callWebpage, url, openOptions, writeScreenshotBytes) {
|
|
245
|
+
const normalizedOptions = normalizeWebpageOpenOptions(openOptions);
|
|
246
|
+
let openUrl;
|
|
247
|
+
if (url !== undefined && url !== null) {
|
|
248
|
+
const trimmed = String(url).trim();
|
|
249
|
+
if (!trimmed) {
|
|
250
|
+
throw new Error('hc.webpage requires a non-empty url when provided');
|
|
251
|
+
}
|
|
252
|
+
openUrl = trimmed;
|
|
253
|
+
}
|
|
254
|
+
const opened = /** @type {{
|
|
255
|
+
tabId: string;
|
|
256
|
+
url: string;
|
|
257
|
+
title: string;
|
|
258
|
+
canGoBack?: boolean;
|
|
259
|
+
canGoForward?: boolean;
|
|
260
|
+
}} */ (
|
|
261
|
+
unwrapWebpageBridgeResult(
|
|
262
|
+
await callWebpage({
|
|
263
|
+
op: 'open',
|
|
264
|
+
url: openUrl,
|
|
265
|
+
reuse: normalizedOptions.reuse
|
|
266
|
+
})
|
|
267
|
+
)
|
|
268
|
+
);
|
|
269
|
+
if (!opened || typeof opened.tabId !== 'string') {
|
|
270
|
+
throw new Error('hc.webpage open did not return a tab');
|
|
271
|
+
}
|
|
272
|
+
return createWebpageHandle(opened, callWebpage, writeScreenshotBytes);
|
|
273
|
+
}
|
package/dist/snippets.d.ts
CHANGED
|
@@ -239,6 +239,8 @@ interface HcInfoApi {
|
|
|
239
239
|
readonly workflowActionId: string;
|
|
240
240
|
/** 0-based index of the workflow action currently executing, or -1 when not in a workflow. */
|
|
241
241
|
readonly workflowActionIteration: number;
|
|
242
|
+
/** UUID of the live page (website) for this script run, or empty when not a live page. */
|
|
243
|
+
readonly livepageId: string;
|
|
242
244
|
}
|
|
243
245
|
|
|
244
246
|
/**
|
|
@@ -266,6 +268,62 @@ interface HcSendRequestResponse {
|
|
|
266
268
|
json(): unknown;
|
|
267
269
|
}
|
|
268
270
|
|
|
271
|
+
/**
|
|
272
|
+
* Live DOM helpers on a webpage handle from hc.webpage.
|
|
273
|
+
*/
|
|
274
|
+
interface HcWebpageDom {
|
|
275
|
+
/**
|
|
276
|
+
* Queries the live page DOM with a CSS selector.
|
|
277
|
+
*/
|
|
278
|
+
query(
|
|
279
|
+
selector: string,
|
|
280
|
+
options?: { all?: boolean; maxElements?: number }
|
|
281
|
+
): Promise<{ selector: string; matchCount: number; elements: unknown[] }>;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Evaluates JavaScript in the page main world and returns the result.
|
|
285
|
+
*/
|
|
286
|
+
evaluate(expression: string): Promise<unknown>;
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Injects and runs JavaScript source in the page main world.
|
|
290
|
+
*/
|
|
291
|
+
injectScript(source: string): Promise<unknown>;
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Injects a CSS stylesheet into the page.
|
|
295
|
+
*/
|
|
296
|
+
injectStylesheet(css: string): Promise<string>;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Handle returned by hc.webpage for an embedded browser tab.
|
|
301
|
+
*/
|
|
302
|
+
interface HcWebpageHandle {
|
|
303
|
+
readonly tabId: string;
|
|
304
|
+
readonly url: string;
|
|
305
|
+
readonly title: string;
|
|
306
|
+
readonly canGoBack: boolean;
|
|
307
|
+
readonly canGoForward: boolean;
|
|
308
|
+
readonly dom: HcWebpageDom;
|
|
309
|
+
/**
|
|
310
|
+
* Focuses this browser tab in the tab bar.
|
|
311
|
+
*/
|
|
312
|
+
focus(): Promise<void>;
|
|
313
|
+
/**
|
|
314
|
+
* Closes this browser tab. Returns false when the user cancels a leave prompt.
|
|
315
|
+
*/
|
|
316
|
+
close(): Promise<boolean>;
|
|
317
|
+
/**
|
|
318
|
+
* Captures the visible viewport as PNG and writes it under the script file access root.
|
|
319
|
+
*
|
|
320
|
+
* @param path - Relative or absolute path under the script file root.
|
|
321
|
+
* @param options - Optional `{ fullPage }` (default false).
|
|
322
|
+
* @returns Absolute path of the written PNG.
|
|
323
|
+
*/
|
|
324
|
+
screenshot(path: string, options?: { fullPage?: boolean }): Promise<{ path: string }>;
|
|
325
|
+
}
|
|
326
|
+
|
|
269
327
|
/**
|
|
270
328
|
* Element surface exposed by hc.response.document() for HTML bodies.
|
|
271
329
|
*/
|
|
@@ -403,6 +461,15 @@ interface HcScriptApi {
|
|
|
403
461
|
* Omit model (or options) to use the first available model.
|
|
404
462
|
*/
|
|
405
463
|
ask(prompt: string, options?: HcAskOptions): Promise<string | null>;
|
|
464
|
+
/**
|
|
465
|
+
* Opens or reuses an embedded browser tab and returns a control handle.
|
|
466
|
+
* Requires Settings → General → Allow script webpage access.
|
|
467
|
+
*
|
|
468
|
+
* @param url - Optional URL to open or reuse; omit to bind the active browser tab.
|
|
469
|
+
* @param options - Optional `{ reuse }` (default true).
|
|
470
|
+
* @throws When webpage access is disabled or unavailable in this context.
|
|
471
|
+
*/
|
|
472
|
+
webpage(url?: string, options?: { reuse?: boolean }): Promise<HcWebpageHandle>;
|
|
406
473
|
/**
|
|
407
474
|
* Resolves after the given delay. Use for pacing between script steps.
|
|
408
475
|
*
|