@mearl/browser-core 3.0.0-beta.4 → 3.0.0-beta.5
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/README.md +1 -1
- package/dist/app-profile-runtime/types.d.ts +6 -4
- package/dist/appProfileDefinition.d.ts +28 -7
- package/dist/appProfileDefinition.js +52 -20
- package/dist/browserActionProtocol.d.ts +19 -13
- package/dist/browserActionProtocol.js +9 -9
- package/dist/browserCapabilities.d.ts +45 -0
- package/dist/browserCapabilities.js +49 -0
- package/dist/cookie.d.ts +6 -0
- package/dist/cookie.js +20 -5
- package/dist/dataStructure.d.ts +8 -0
- package/dist/dataStructure.js +58 -0
- package/dist/domClicking.js +52 -0
- package/dist/domInspection.js +13 -1
- package/dist/extensionServiceProtocol.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/proxyPermissions.js +1 -0
- package/dist/requestSerializer.js +1 -29
- package/dist/runActions.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
浏览器与 Node 可共享的页面控制逻辑和协议:DOM/AX 快照、动作观察、输入、滚动、导航、请求序列化、捕获游标、设备模拟、通用设备外观、Cookie/域名权限和插件任务、页面、状态合同。模块没有浏览器扩展或本机进程的安装副作用。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
核心不预置 App 身份;档案 ID 由宿主的已激活能力目录校验。App 档案 schema、显式选项、revision 和页面运行时绑定是数据合同,完整桥实现由独立能力包提供。
|
|
6
6
|
|
|
7
7
|
`@mearl/browser-core/appProfileRuntime` 的内置安装器只同步 UA、navigator.appVersion、navigator.platform 和触摸点,不安装 App 桥、业务全局变量、容器栈或授权处理。有 pageRuntime 绑定时,宿主必须先从已安装的能力包准备确切代码,再通过页面运行时派发器执行;不能调用基础安装器代替。资源准备和派发见 [能力包](../../docs/browser-bundles.md#页面运行时)。
|
|
8
8
|
|
|
@@ -2,7 +2,7 @@ import type { AppProfileBinding, AppProfilePolicies, AppProfileSetting } from '.
|
|
|
2
2
|
import type { BrowserDevicePresetName } from '../browserActionProtocol.js';
|
|
3
3
|
import type { DeviceCutout } from '../deviceEmulation.js';
|
|
4
4
|
/** 桥调用风格,与页面适配器使用的端内调用约定一一对应。 */
|
|
5
|
-
export type AppBridgeStyle =
|
|
5
|
+
export type AppBridgeStyle = string;
|
|
6
6
|
export interface AppBridgeSpec {
|
|
7
7
|
global: string;
|
|
8
8
|
style: AppBridgeStyle;
|
|
@@ -10,7 +10,7 @@ export interface AppBridgeSpec {
|
|
|
10
10
|
persistent?: boolean;
|
|
11
11
|
}
|
|
12
12
|
export type AppBridgeBehavior = 'navigate' | 'closeAll' | 'closeBackData' | 'postMessage' | 'notifySubscribe' | 'notifyUnsubscribe' | 'notifyPost' | 'sharedFileRead' | 'sharedFileWrite' | 'sharedDataGet' | 'sharedDataSet' | 'legacyBroadcastCreate' | 'legacyBroadcastPost' | 'legacyBroadcastClose' | 'memoryGet' | 'memorySet' | 'memoryDelete' | 'availabilityMap' | 'userInfo' | 'authCode' | 'networkInfo' | 'geolocation' | 'permission' | 'imagePicker' | 'prefetch' | 'fptTrack' | 'mtopStream' | 'previewFile' | 'toast' | 'unsupportedNativeInput' | 'payment' | 'userTrack' | 'calendar' | 'networkQualityMonitor' | 'beaconRequest' | 'voiceRecognition';
|
|
13
|
-
/**
|
|
13
|
+
/** 桥方法名到标准化动作的映射。命名空间桥使用 `Class.method`,其余使用 api 名。 */
|
|
14
14
|
export interface BridgeActionMap {
|
|
15
15
|
open: string[];
|
|
16
16
|
close: string[];
|
|
@@ -68,14 +68,14 @@ export interface AppMiniProgramMessageRule {
|
|
|
68
68
|
}
|
|
69
69
|
export type AppMiniProgramNativeAction = 'navigateMiniProgram' | 'subscribeMessage';
|
|
70
70
|
export interface AppMiniProgramNativeRoute {
|
|
71
|
-
/**
|
|
71
|
+
/** 承接原生能力的小程序页面 pathname。 */
|
|
72
72
|
pathname: string;
|
|
73
73
|
/** 该页面允许模拟的固定原生动作,动作名读取 URL 的 action 参数。 */
|
|
74
74
|
actions: AppMiniProgramNativeAction[];
|
|
75
75
|
/** 限制业务根域,避免其他小程序的同名页面被接管。 */
|
|
76
76
|
hostSuffixes?: string[];
|
|
77
77
|
}
|
|
78
|
-
/**
|
|
78
|
+
/** 小程序容器相关规则统一收口,避免为每种业务页增加顶层配置。 */
|
|
79
79
|
export interface AppMiniProgramConfig {
|
|
80
80
|
paymentRoutes?: AppMiniProgramPaymentRoute[];
|
|
81
81
|
nativeRoutes?: AppMiniProgramNativeRoute[];
|
|
@@ -187,6 +187,8 @@ export interface InjectedAppProfileConfig {
|
|
|
187
187
|
binding?: AppProfileBinding;
|
|
188
188
|
policies?: AppProfilePolicies;
|
|
189
189
|
settings?: AppProfileSetting[];
|
|
190
|
+
settingsUi?: 'runtime';
|
|
191
|
+
settingsGroup?: string;
|
|
190
192
|
profile: string;
|
|
191
193
|
label: string;
|
|
192
194
|
globals: Record<string, string>;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { BrowserDevicePresetName, AppBridgeConfig } from './browserActionProtocol.js';
|
|
2
2
|
import type { AppBridgeSpec, AppImagePickerConfig, AppLayerUrlProxyConfig, AppMiniProgramConfig, AppNativeBackConfig, AppOverlayConfig, AppSchemeRule, AppTitleBarHostStyle, BridgeActionMap } from './app-profile-runtime/types.js';
|
|
3
3
|
export interface AppProfilePolicies {
|
|
4
|
-
titleBar?:
|
|
4
|
+
titleBar?: string;
|
|
5
5
|
navigatorLanguage?: boolean;
|
|
6
|
-
networkResult?:
|
|
6
|
+
networkResult?: string;
|
|
7
7
|
languages?: string[];
|
|
8
8
|
defaultLanguage?: string;
|
|
9
9
|
}
|
|
@@ -56,28 +56,49 @@ export interface AppProfileCatalogEntry {
|
|
|
56
56
|
revision: string;
|
|
57
57
|
definition: AppProfileDefinition;
|
|
58
58
|
}
|
|
59
|
+
/** Short, author-supplied guidance; discovery does not load Skills or follow links. */
|
|
60
|
+
export interface AppProfileUsage {
|
|
61
|
+
description?: string;
|
|
62
|
+
limitations?: string[];
|
|
63
|
+
skills?: string[];
|
|
64
|
+
documentation?: Array<{
|
|
65
|
+
title: string;
|
|
66
|
+
url: string;
|
|
67
|
+
}>;
|
|
68
|
+
}
|
|
59
69
|
/** 单容器档案(纯数据) */
|
|
60
70
|
export interface AppProfileDefinition {
|
|
61
71
|
name: string;
|
|
72
|
+
usage?: AppProfileUsage;
|
|
62
73
|
/** Content-addressed installer supplied by the selected browser capability package. */
|
|
63
74
|
pageRuntime?: {
|
|
64
75
|
id: string;
|
|
65
76
|
sha256: string;
|
|
66
77
|
};
|
|
67
78
|
label: string;
|
|
68
|
-
|
|
79
|
+
actionIcon?: string;
|
|
80
|
+
labels?: {
|
|
81
|
+
en: string;
|
|
82
|
+
zh_CN?: string;
|
|
83
|
+
};
|
|
84
|
+
/** 容器的应用标识,由能力包定义 */
|
|
69
85
|
appName: string;
|
|
70
86
|
defaultAppVersion: string;
|
|
71
|
-
/** UA
|
|
72
|
-
uaFamily?:
|
|
87
|
+
/** UA 主体格式;未提供时使用标准浏览器 UA */
|
|
88
|
+
uaFamily?: string;
|
|
73
89
|
uaTemplate?: {
|
|
74
90
|
ios: string;
|
|
75
91
|
android: string;
|
|
76
92
|
};
|
|
77
93
|
policies?: AppProfilePolicies;
|
|
78
94
|
settings?: AppProfileSetting[];
|
|
79
|
-
|
|
80
|
-
|
|
95
|
+
authorization?: {
|
|
96
|
+
hook: string;
|
|
97
|
+
method: string;
|
|
98
|
+
};
|
|
99
|
+
settingsUi?: 'runtime';
|
|
100
|
+
settingsGroup?: string;
|
|
101
|
+
/** UA 主体之后追加的 token,{v} 占位会被替换为 appVersion */
|
|
81
102
|
uaTokens: string[];
|
|
82
103
|
/** 额外注入到 window 的变量(公共变量由 resolve 统一生成) */
|
|
83
104
|
globals: Record<string, string>;
|
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
const BRIDGE_STYLES = new Set([
|
|
2
|
-
'windvane',
|
|
3
|
-
'fliggy',
|
|
4
|
-
'alipay',
|
|
5
|
-
'callback',
|
|
6
|
-
'btrip',
|
|
7
|
-
'invoke',
|
|
8
|
-
'callHandler',
|
|
9
|
-
'amap',
|
|
10
|
-
'didi',
|
|
11
|
-
'easybikeNative',
|
|
12
|
-
'mega',
|
|
13
|
-
'wechat',
|
|
14
|
-
'wechatMiniProgram',
|
|
15
|
-
]);
|
|
16
1
|
export const APP_BRIDGE_BEHAVIORS = [
|
|
17
2
|
'navigate',
|
|
18
3
|
'closeAll',
|
|
@@ -133,12 +118,17 @@ export function validateAppProfileDefinition(value) {
|
|
|
133
118
|
'schemes',
|
|
134
119
|
];
|
|
135
120
|
const optional = [
|
|
121
|
+
'usage',
|
|
122
|
+
'actionIcon',
|
|
123
|
+
'labels',
|
|
136
124
|
'pageRuntime',
|
|
137
125
|
'uaFamily',
|
|
138
126
|
'uaTemplate',
|
|
139
127
|
'policies',
|
|
140
128
|
'settings',
|
|
141
129
|
'settingsGroup',
|
|
130
|
+
'settingsUi',
|
|
131
|
+
'authorization',
|
|
142
132
|
'miniProgram',
|
|
143
133
|
'imagePicker',
|
|
144
134
|
'geolocation',
|
|
@@ -160,6 +150,37 @@ export function validateAppProfileDefinition(value) {
|
|
|
160
150
|
invalid(key);
|
|
161
151
|
if (!/^[a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)?$/.test(value.name))
|
|
162
152
|
invalid('name');
|
|
153
|
+
if (value.usage !== undefined) {
|
|
154
|
+
const usage = value.usage;
|
|
155
|
+
fields(usage, [], ['description', 'limitations', 'skills', 'documentation'], 'usage');
|
|
156
|
+
if (new TextEncoder().encode(JSON.stringify(usage)).byteLength > 4096)
|
|
157
|
+
invalid('usage exceeds 4 KiB');
|
|
158
|
+
if (usage.description !== undefined && (!text(usage.description) || usage.description.length > 512))
|
|
159
|
+
invalid('usage.description');
|
|
160
|
+
for (const key of ['limitations', 'skills']) {
|
|
161
|
+
const items = usage[key];
|
|
162
|
+
if (items !== undefined && (!Array.isArray(items) || items.length > 8 || items.some(item => !text(item) || !item || item.length > 256)))
|
|
163
|
+
invalid(`usage.${key}`);
|
|
164
|
+
}
|
|
165
|
+
if (usage.documentation !== undefined) {
|
|
166
|
+
if (!Array.isArray(usage.documentation) || usage.documentation.length > 8)
|
|
167
|
+
invalid('usage.documentation');
|
|
168
|
+
for (const link of usage.documentation) {
|
|
169
|
+
fields(link, ['title', 'url'], [], 'usage.documentation');
|
|
170
|
+
if (!text(link.title) || !link.title || link.title.length > 128 || !text(link.url) || link.url.length > 1024)
|
|
171
|
+
invalid('usage.documentation');
|
|
172
|
+
let url;
|
|
173
|
+
try {
|
|
174
|
+
url = new URL(link.url);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
invalid('usage.documentation.url');
|
|
178
|
+
}
|
|
179
|
+
if (url.protocol !== 'https:' || url.username || url.password)
|
|
180
|
+
invalid('usage.documentation.url');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
163
184
|
if (value.pageRuntime !== undefined) {
|
|
164
185
|
fields(value.pageRuntime, ['id', 'sha256'], [], 'pageRuntime');
|
|
165
186
|
const runtime = value.pageRuntime;
|
|
@@ -191,17 +212,28 @@ export function validateAppProfileDefinition(value) {
|
|
|
191
212
|
.split('.')
|
|
192
213
|
.some(part => ['__proto__', 'prototype', 'constructor'].includes(part)) ||
|
|
193
214
|
globals.has(bridge.global) ||
|
|
194
|
-
|
|
215
|
+
(typeof bridge.style !== 'string' || !/^[a-zA-Z][a-zA-Z0-9-]{0,63}$/.test(bridge.style)) ||
|
|
195
216
|
(bridge.persistent !== undefined && typeof bridge.persistent !== 'boolean'))
|
|
196
217
|
invalid('bridge');
|
|
197
218
|
globals.add(bridge.global);
|
|
198
219
|
}
|
|
199
220
|
validateActions(value.actions);
|
|
221
|
+
if (value.labels !== undefined) {
|
|
222
|
+
fields(value.labels, ['en'], ['zh_CN'], 'labels');
|
|
223
|
+
if (!text(value.labels.en) || !value.labels.en || value.labels.en.length > 120 || value.labels.zh_CN !== undefined && (!text(value.labels.zh_CN) || value.labels.zh_CN.length > 120))
|
|
224
|
+
invalid('labels');
|
|
225
|
+
}
|
|
226
|
+
if (value.actionIcon !== undefined && (typeof value.actionIcon !== 'string' || value.actionIcon.length > 32768 || !/^data:image\/png;base64,iVBORw0KGgo[A-Za-z0-9+/]*={0,2}$/.test(value.actionIcon)))
|
|
227
|
+
invalid('actionIcon');
|
|
228
|
+
if (value.authorization !== undefined && (!record(value.authorization) || Object.keys(value.authorization).sort().join(',') !== 'hook,method' || typeof value.authorization.hook !== 'string' || !/^[a-z][a-zA-Z0-9_.-]{0,95}$/.test(value.authorization.hook) || typeof value.authorization.method !== 'string' || !value.authorization.method || value.authorization.method.length > 200))
|
|
229
|
+
invalid('authorization');
|
|
230
|
+
if (value.settingsUi !== undefined && value.settingsUi !== 'runtime')
|
|
231
|
+
throw new Error('Invalid App settings UI');
|
|
200
232
|
if (value.settingsGroup !== undefined &&
|
|
201
|
-
|
|
233
|
+
(typeof value.settingsGroup !== 'string' || value.settingsGroup.length > 64))
|
|
202
234
|
invalid('settingsGroup');
|
|
203
235
|
if (value.uaFamily !== undefined &&
|
|
204
|
-
|
|
236
|
+
(typeof value.uaFamily !== 'string' || value.uaFamily.length > 64))
|
|
205
237
|
invalid('uaFamily');
|
|
206
238
|
if (value.uaTemplate !== undefined) {
|
|
207
239
|
fields(value.uaTemplate, ['ios', 'android'], [], 'uaTemplate');
|
|
@@ -212,9 +244,9 @@ export function validateAppProfileDefinition(value) {
|
|
|
212
244
|
fields(value.policies, [], ['titleBar', 'navigatorLanguage', 'networkResult', 'languages', 'defaultLanguage'], 'policies');
|
|
213
245
|
const p = value.policies;
|
|
214
246
|
if (p.titleBar !== undefined &&
|
|
215
|
-
|
|
247
|
+
(typeof p.titleBar !== 'string' || p.titleBar.length > 64))
|
|
216
248
|
invalid('policies.titleBar');
|
|
217
|
-
if (p.networkResult !== undefined &&
|
|
249
|
+
if (p.networkResult !== undefined && (typeof p.networkResult !== 'string' || p.networkResult.length > 64))
|
|
218
250
|
invalid('policies.networkResult');
|
|
219
251
|
if (p.navigatorLanguage !== undefined && typeof p.navigatorLanguage !== 'boolean')
|
|
220
252
|
invalid('policies.navigatorLanguage');
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import type { BrowserCapabilityCatalog } from './browserCapabilities.js';
|
|
2
|
+
export interface GetCapabilitiesParams {
|
|
3
|
+
/** Include method and setting details for this installed App ID. */
|
|
4
|
+
app?: string;
|
|
5
|
+
}
|
|
1
6
|
export interface GetRequestsParams {
|
|
2
7
|
tabId: number;
|
|
3
8
|
count?: number;
|
|
@@ -486,8 +491,8 @@ export interface TabOpenParams {
|
|
|
486
491
|
emulation?: BrowserEmulationOptions;
|
|
487
492
|
group?: string;
|
|
488
493
|
/**
|
|
489
|
-
*
|
|
490
|
-
* 设备视口和 iframe 系统外壳由 emulation.deviceShell
|
|
494
|
+
* 客户端容器模拟档案名(由已安装能力目录提供)。只注入容器 UA、环境变量、JSBridge 与桥协议;
|
|
495
|
+
* 设备视口和 iframe 系统外壳由 emulation.deviceShell 独立控制。可用档案通过 get_capabilities 查询。
|
|
491
496
|
*/
|
|
492
497
|
appProfile?: AppProfileName;
|
|
493
498
|
/** 与 appProfile 配合使用;容器系统,默认 ios */
|
|
@@ -504,16 +509,9 @@ export type TabGroupParams = {
|
|
|
504
509
|
ungroup: true;
|
|
505
510
|
title?: never;
|
|
506
511
|
};
|
|
507
|
-
/**
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
* 档案数据(UA / 桥对象 / 方法映射)在 @mearl/app-profiles 维护,但名单属于协议:
|
|
511
|
-
* 它是 set_app_profile.profile 与 tab_open.appProfile 的取值域,CLI 帮助与 MCP 工具
|
|
512
|
-
* schema 的 enum 也由它生成。app-profiles 反向引用本清单约束自己的档案表,
|
|
513
|
-
* 因此新增档案时只需改两处(名单 + 档案数据),且漏改会在类型检查时暴露。
|
|
514
|
-
*/
|
|
515
|
-
export declare const APP_PROFILE_NAMES: readonly ["fliggy", "taobao", "wechat", "alipay"];
|
|
516
|
-
export type BuiltinAppProfileName = (typeof APP_PROFILE_NAMES)[number];
|
|
512
|
+
/** App identities are supplied by installed capability catalogs. */
|
|
513
|
+
export declare const APP_PROFILE_NAMES: readonly string[];
|
|
514
|
+
export type BuiltinAppProfileName = string;
|
|
517
515
|
/** Hosts validate dynamic profile IDs against the active capability catalog. */
|
|
518
516
|
export type AppProfileName = string;
|
|
519
517
|
export interface SetAppProfileParams {
|
|
@@ -765,7 +763,7 @@ export interface SetCookieResult {
|
|
|
765
763
|
export interface RequestDomainPermissionParams {
|
|
766
764
|
domain: string;
|
|
767
765
|
}
|
|
768
|
-
export type RunActionName = Exclude<ExtensionBrowserAction, 'run_actions' | 'browser_release'>;
|
|
766
|
+
export type RunActionName = Exclude<ExtensionBrowserAction, 'run_actions' | 'browser_release' | 'get_capabilities'>;
|
|
769
767
|
export interface ActionStep {
|
|
770
768
|
action: RunActionName;
|
|
771
769
|
data?: Record<string, unknown>;
|
|
@@ -775,6 +773,10 @@ export interface RunActionsParams {
|
|
|
775
773
|
tabId: number;
|
|
776
774
|
}
|
|
777
775
|
export interface BrowserActionMap {
|
|
776
|
+
get_capabilities: {
|
|
777
|
+
request: GetCapabilitiesParams;
|
|
778
|
+
response: BrowserCapabilityCatalog;
|
|
779
|
+
};
|
|
778
780
|
tab_checkpoint: {
|
|
779
781
|
request: CaptureCheckpointParams;
|
|
780
782
|
response: CaptureCheckpointResult;
|
|
@@ -979,6 +981,7 @@ export declare const BROWSER_ACTIONS: {
|
|
|
979
981
|
readonly get_mocks: true;
|
|
980
982
|
readonly set_rule: true;
|
|
981
983
|
readonly get_rules: true;
|
|
984
|
+
readonly get_capabilities: true;
|
|
982
985
|
readonly send_request: true;
|
|
983
986
|
readonly send_mtop_request: true;
|
|
984
987
|
readonly browser_list: true;
|
|
@@ -1025,6 +1028,7 @@ export declare const BROWSER_COMMAND_ACTIONS: {
|
|
|
1025
1028
|
readonly get_mocks: true;
|
|
1026
1029
|
readonly set_rule: true;
|
|
1027
1030
|
readonly get_rules: true;
|
|
1031
|
+
readonly get_capabilities: true;
|
|
1028
1032
|
readonly send_request: true;
|
|
1029
1033
|
readonly send_mtop_request: true;
|
|
1030
1034
|
readonly browser_list: true;
|
|
@@ -1070,6 +1074,7 @@ export declare const EXTENSION_BROWSER_ACTIONS: {
|
|
|
1070
1074
|
readonly get_mocks: true;
|
|
1071
1075
|
readonly set_rule: true;
|
|
1072
1076
|
readonly get_rules: true;
|
|
1077
|
+
readonly get_capabilities: true;
|
|
1073
1078
|
readonly send_request: true;
|
|
1074
1079
|
readonly send_mtop_request: true;
|
|
1075
1080
|
readonly browser_release: true;
|
|
@@ -1111,6 +1116,7 @@ export declare const BROWSER_CAPABILITY_ACTIONS: {
|
|
|
1111
1116
|
readonly get_mocks: true;
|
|
1112
1117
|
readonly set_rule: true;
|
|
1113
1118
|
readonly get_rules: true;
|
|
1119
|
+
readonly get_capabilities: true;
|
|
1114
1120
|
readonly send_request: true;
|
|
1115
1121
|
readonly send_mtop_request: true;
|
|
1116
1122
|
readonly browser_release: true;
|
|
@@ -20,15 +20,8 @@ export const BROWSER_DEVICE_PRESET_NAMES = [
|
|
|
20
20
|
'ipad-mini',
|
|
21
21
|
'galaxy-s23',
|
|
22
22
|
];
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
*
|
|
26
|
-
* 档案数据(UA / 桥对象 / 方法映射)在 @mearl/app-profiles 维护,但名单属于协议:
|
|
27
|
-
* 它是 set_app_profile.profile 与 tab_open.appProfile 的取值域,CLI 帮助与 MCP 工具
|
|
28
|
-
* schema 的 enum 也由它生成。app-profiles 反向引用本清单约束自己的档案表,
|
|
29
|
-
* 因此新增档案时只需改两处(名单 + 档案数据),且漏改会在类型检查时暴露。
|
|
30
|
-
*/
|
|
31
|
-
export const APP_PROFILE_NAMES = ['fliggy', 'taobao', 'wechat', 'alipay'];
|
|
23
|
+
/** App identities are supplied by installed capability catalogs. */
|
|
24
|
+
export const APP_PROFILE_NAMES = [];
|
|
32
25
|
export function resolvePageEvalExpression(params) {
|
|
33
26
|
if (typeof params.expression !== 'string' || !params.expression.trim())
|
|
34
27
|
throw new Error('Invalid expression');
|
|
@@ -83,6 +76,10 @@ export function actionRequiresTabId(action) {
|
|
|
83
76
|
return typeof action === 'string' && TAB_ID_REQUIRED_ACTION_SET.has(action);
|
|
84
77
|
}
|
|
85
78
|
export function validateRequiredActionTabId(action, data) {
|
|
79
|
+
if (action === 'get_capabilities' && data && typeof data === 'object' &&
|
|
80
|
+
['tabId', 'tabIds', 'openerTabId'].some(key => Object.hasOwn(data, key))) {
|
|
81
|
+
throw new Error('get_capabilities does not accept page targets');
|
|
82
|
+
}
|
|
86
83
|
const cookieAction = action === 'get_cookie' || action === 'set_cookie';
|
|
87
84
|
if (!actionRequiresTabId(action) && !cookieAction)
|
|
88
85
|
return;
|
|
@@ -123,6 +120,7 @@ export const BROWSER_ACTIONS = {
|
|
|
123
120
|
get_mocks: true,
|
|
124
121
|
set_rule: true,
|
|
125
122
|
get_rules: true,
|
|
123
|
+
get_capabilities: true,
|
|
126
124
|
send_request: true,
|
|
127
125
|
send_mtop_request: true,
|
|
128
126
|
browser_list: true,
|
|
@@ -169,6 +167,7 @@ export const BROWSER_COMMAND_ACTIONS = {
|
|
|
169
167
|
get_mocks: true,
|
|
170
168
|
set_rule: true,
|
|
171
169
|
get_rules: true,
|
|
170
|
+
get_capabilities: true,
|
|
172
171
|
send_request: true,
|
|
173
172
|
send_mtop_request: true,
|
|
174
173
|
browser_list: true,
|
|
@@ -214,6 +213,7 @@ export const EXTENSION_BROWSER_ACTIONS = {
|
|
|
214
213
|
get_mocks: true,
|
|
215
214
|
set_rule: true,
|
|
216
215
|
get_rules: true,
|
|
216
|
+
get_capabilities: true,
|
|
217
217
|
send_request: true,
|
|
218
218
|
send_mtop_request: true,
|
|
219
219
|
browser_release: true,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { AppProfileCatalogEntry, AppProfileSetting, AppProfileUsage } from './appProfileDefinition.js';
|
|
2
|
+
import type { GetCapabilitiesParams } from './browserActionProtocol.js';
|
|
3
|
+
export interface AppCapability {
|
|
4
|
+
id: string;
|
|
5
|
+
label: string;
|
|
6
|
+
source: AppProfileCatalogEntry['source'];
|
|
7
|
+
plugin?: string;
|
|
8
|
+
available: boolean;
|
|
9
|
+
features: string[];
|
|
10
|
+
usage?: AppProfileUsage;
|
|
11
|
+
details?: {
|
|
12
|
+
defaultAppVersion: string;
|
|
13
|
+
bridges: string[];
|
|
14
|
+
methods: Record<string, string[]>;
|
|
15
|
+
behaviors: Record<string, string>;
|
|
16
|
+
settings: AppProfileSetting[];
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export interface BrowserCapabilityCatalog {
|
|
20
|
+
/** Supported operations, not a grant of page permissions or business authorization. */
|
|
21
|
+
actions: string[];
|
|
22
|
+
devicePresets: string[];
|
|
23
|
+
apps: {
|
|
24
|
+
status: 'available' | 'partial' | 'unsupported';
|
|
25
|
+
entries: AppCapability[];
|
|
26
|
+
issues: Array<{
|
|
27
|
+
plugin: string;
|
|
28
|
+
error: string;
|
|
29
|
+
}>;
|
|
30
|
+
};
|
|
31
|
+
limitations: string[];
|
|
32
|
+
help: {
|
|
33
|
+
skill: string;
|
|
34
|
+
commands: string[];
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Project validated installed metadata without scripts, global values or Mock response bodies. */
|
|
38
|
+
export declare function describeBrowserCapabilities(actions: readonly string[], catalog: {
|
|
39
|
+
profiles: AppProfileCatalogEntry[];
|
|
40
|
+
errors: Array<{
|
|
41
|
+
plugin: string;
|
|
42
|
+
error: string;
|
|
43
|
+
}>;
|
|
44
|
+
offline?: boolean;
|
|
45
|
+
} | undefined, params?: GetCapabilitiesParams): BrowserCapabilityCatalog;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { BROWSER_DEVICE_PRESET_NAMES } from './browserActionProtocol.js';
|
|
2
|
+
/** Project validated installed metadata without scripts, global values or Mock response bodies. */
|
|
3
|
+
export function describeBrowserCapabilities(actions, catalog, params = {}) {
|
|
4
|
+
if (!params || typeof params !== 'object' || Array.isArray(params))
|
|
5
|
+
throw new Error('Invalid capability query');
|
|
6
|
+
if (params.app !== undefined && (typeof params.app !== 'string' || !params.app || params.app.length > 161))
|
|
7
|
+
throw new Error('Invalid App capability ID');
|
|
8
|
+
if (params.app && catalog && !catalog.offline && !catalog.errors.length && !catalog.profiles.some(entry => entry.id === params.app))
|
|
9
|
+
throw new Error(`App profile is not installed: ${params.app}`);
|
|
10
|
+
const supportsApps = actions.includes('set_app_profile');
|
|
11
|
+
const profiles = supportsApps ? catalog?.profiles ?? [] : [];
|
|
12
|
+
return {
|
|
13
|
+
actions: [...new Set(actions)],
|
|
14
|
+
devicePresets: actions.includes('set_device_emulation') ? [...BROWSER_DEVICE_PRESET_NAMES] : [],
|
|
15
|
+
apps: {
|
|
16
|
+
status: !supportsApps || !catalog ? 'unsupported' : catalog.offline || catalog.errors.length || profiles.some(entry => entry.available === false) ? 'partial' : 'available',
|
|
17
|
+
entries: profiles.filter(entry => !params.app || entry.id === params.app).map(entry => {
|
|
18
|
+
const definition = entry.definition;
|
|
19
|
+
const methods = {};
|
|
20
|
+
for (const [name, values] of Object.entries(definition.actions)) {
|
|
21
|
+
const names = Array.isArray(values) ? values : Object.keys(values ?? {});
|
|
22
|
+
if (names.length)
|
|
23
|
+
methods[name] = [...names];
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
id: entry.id, label: entry.label, source: entry.source, plugin: entry.plugin,
|
|
27
|
+
available: entry.available !== false,
|
|
28
|
+
features: ['userAgent', ...(definition.bridges.length ? ['jsBridge'] : []), ...Object.keys(methods), ...(definition.schemes.length ? ['schemes'] : []), ...(definition.settings?.length ? ['settings'] : [])],
|
|
29
|
+
...(definition.usage ? { usage: structuredClone(definition.usage) } : {}),
|
|
30
|
+
...(params.app ? { details: {
|
|
31
|
+
defaultAppVersion: definition.defaultAppVersion,
|
|
32
|
+
bridges: definition.bridges.map(bridge => bridge.global),
|
|
33
|
+
methods,
|
|
34
|
+
behaviors: { ...definition.actions.behaviors },
|
|
35
|
+
settings: structuredClone(definition.settings ?? []),
|
|
36
|
+
} } : {}),
|
|
37
|
+
};
|
|
38
|
+
}),
|
|
39
|
+
issues: catalog?.errors.map(issue => ({ ...issue })) ?? [],
|
|
40
|
+
},
|
|
41
|
+
limitations: [
|
|
42
|
+
'Actions describe support, not current domain permissions, login state or device connectivity.',
|
|
43
|
+
'App profiles simulate a WebView. Declared bridge methods, behaviors and response stubs do not guarantee real native authorization, payment or other business effects.',
|
|
44
|
+
'Package usage and Skill names are guidance from the package author; referenced Skills may need separate installation. Read the relevant help only when needed.',
|
|
45
|
+
...(!catalog && supportsApps ? ['This Provider does not expose an installed App catalog.'] : []),
|
|
46
|
+
],
|
|
47
|
+
help: { skill: 'mearl', commands: ['mearl get_capabilities --help', 'mearl set_device_emulation --help', 'mearl set_app_profile --help', 'mearl tab_open --help'] },
|
|
48
|
+
};
|
|
49
|
+
}
|
package/dist/cookie.d.ts
CHANGED
|
@@ -25,6 +25,12 @@ export interface CookieCandidate {
|
|
|
25
25
|
partitionKey?: unknown;
|
|
26
26
|
partitionKeyOpaque?: boolean;
|
|
27
27
|
}
|
|
28
|
+
export type RequestCookie = Pick<CookieCandidate, 'name' | 'value'>;
|
|
29
|
+
export declare function serializeRequestCookies(cookies: readonly RequestCookie[]): string;
|
|
30
|
+
/** Token lookup follows the first (most specific path) occurrence in the Cookie header. */
|
|
31
|
+
export declare function requestCookiesToMap(cookies: readonly RequestCookie[]): Map<string, string>;
|
|
32
|
+
/** Chromium also treats localhost and loopback URLs as trustworthy for Secure cookies. */
|
|
33
|
+
export declare function isSecureCookieUrl(url: URL): boolean;
|
|
28
34
|
/** Filter browser-context storage results to the cookies applicable to the requested URL. */
|
|
29
35
|
export declare function filterCookiesForUrl<T extends CookieCandidate>(cookies: readonly T[], cookieUrl: string): T[];
|
|
30
36
|
/** Select one readable, non-partitioned named cookie from URL-filtered backend results. */
|
package/dist/cookie.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
export function serializeRequestCookies(cookies) {
|
|
2
|
+
return cookies.map(cookie => `${cookie.name}=${cookie.value}`).join('; ');
|
|
3
|
+
}
|
|
4
|
+
/** Token lookup follows the first (most specific path) occurrence in the Cookie header. */
|
|
5
|
+
export function requestCookiesToMap(cookies) {
|
|
6
|
+
const values = new Map();
|
|
7
|
+
for (const cookie of cookies) {
|
|
8
|
+
if (!values.has(cookie.name))
|
|
9
|
+
values.set(cookie.name, cookie.value);
|
|
10
|
+
}
|
|
11
|
+
return values;
|
|
12
|
+
}
|
|
1
13
|
function normalizeCookieDomain(input, action) {
|
|
2
14
|
const value = input.trim();
|
|
3
15
|
if (!value)
|
|
@@ -14,21 +26,24 @@ function normalizeCookieDomain(input, action) {
|
|
|
14
26
|
throw new Error(`${action}: invalid domain "${input}"`);
|
|
15
27
|
return hostname;
|
|
16
28
|
}
|
|
17
|
-
/**
|
|
18
|
-
export function
|
|
19
|
-
const url = new URL(cookieUrl);
|
|
29
|
+
/** Chromium also treats localhost and loopback URLs as trustworthy for Secure cookies. */
|
|
30
|
+
export function isSecureCookieUrl(url) {
|
|
20
31
|
const hostname = url.hostname.replace(/\.$/, '');
|
|
21
32
|
const isLocalhost = hostname === 'localhost' ||
|
|
22
33
|
hostname.endsWith('.localhost') ||
|
|
23
34
|
hostname === '[::1]' ||
|
|
24
35
|
/^127\.\d+\.\d+\.\d+$/.test(hostname);
|
|
36
|
+
return url.protocol === 'https:' || isLocalhost;
|
|
37
|
+
}
|
|
38
|
+
/** Filter browser-context storage results to the cookies applicable to the requested URL. */
|
|
39
|
+
export function filterCookiesForUrl(cookies, cookieUrl) {
|
|
40
|
+
const url = new URL(cookieUrl);
|
|
25
41
|
return cookies.filter(item => {
|
|
26
42
|
const domain = item.domain.replace(/^\./, '').toLowerCase();
|
|
27
43
|
const hostOnly = item.hostOnly ?? !item.domain.startsWith('.');
|
|
28
44
|
if (url.hostname !== domain && (hostOnly || !url.hostname.endsWith(`.${domain}`)))
|
|
29
45
|
return false;
|
|
30
|
-
|
|
31
|
-
if (item.secure && url.protocol !== 'https:' && !isLocalhost)
|
|
46
|
+
if (item.secure && !isSecureCookieUrl(url))
|
|
32
47
|
return false;
|
|
33
48
|
return (url.pathname === item.path ||
|
|
34
49
|
(url.pathname.startsWith(item.path) &&
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface StructureOptions {
|
|
2
|
+
maxDepth?: number;
|
|
3
|
+
maxKeys?: number;
|
|
4
|
+
arraySamples?: number;
|
|
5
|
+
maxNodes?: number;
|
|
6
|
+
}
|
|
7
|
+
/** Describes a bounded sample of a value without invoking accessors or exposing leaf values. */
|
|
8
|
+
export declare function getStructure(data: unknown, options?: StructureOptions): unknown;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** Describes a bounded sample of a value without invoking accessors or exposing leaf values. */
|
|
2
|
+
export function getStructure(data, options = {}) {
|
|
3
|
+
const { maxDepth = 5, maxKeys = 50, arraySamples = 1, maxNodes = 2000 } = options;
|
|
4
|
+
for (const [name, value, limit] of [
|
|
5
|
+
['maxDepth', maxDepth, 20],
|
|
6
|
+
['maxKeys', maxKeys, 1000],
|
|
7
|
+
['arraySamples', arraySamples, 20],
|
|
8
|
+
['maxNodes', maxNodes, 10000],
|
|
9
|
+
]) {
|
|
10
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > limit)
|
|
11
|
+
throw new Error(`${name} must be an integer between 1 and ${limit}`);
|
|
12
|
+
}
|
|
13
|
+
const ancestors = new WeakSet();
|
|
14
|
+
let nodes = 0;
|
|
15
|
+
const visit = (value, depth) => {
|
|
16
|
+
if (++nodes > maxNodes)
|
|
17
|
+
return '<max nodes reached>';
|
|
18
|
+
if (depth >= maxDepth)
|
|
19
|
+
return '<max depth reached>';
|
|
20
|
+
if (value === null)
|
|
21
|
+
return 'null';
|
|
22
|
+
if (typeof value !== 'object')
|
|
23
|
+
return typeof value;
|
|
24
|
+
if (ancestors.has(value))
|
|
25
|
+
return '<circular>';
|
|
26
|
+
ancestors.add(value);
|
|
27
|
+
try {
|
|
28
|
+
if (Array.isArray(value)) {
|
|
29
|
+
if (!value.length)
|
|
30
|
+
return 'array[0]';
|
|
31
|
+
const samples = Array.from({ length: Math.min(value.length, arraySamples) }, (_, index) => {
|
|
32
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, index);
|
|
33
|
+
return descriptor && 'value' in descriptor
|
|
34
|
+
? visit(descriptor.value, depth + 1)
|
|
35
|
+
: descriptor
|
|
36
|
+
? '<accessor>'
|
|
37
|
+
: 'undefined';
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
_type: `array[${value.length}]`,
|
|
41
|
+
...(arraySamples === 1 ? { _sample: samples[0] } : { _samples: samples }),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const keys = Object.keys(value);
|
|
45
|
+
const entries = keys.slice(0, maxKeys).map(key => {
|
|
46
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
47
|
+
return [key, 'value' in descriptor ? visit(descriptor.value, depth + 1) : '<accessor>'];
|
|
48
|
+
});
|
|
49
|
+
if (keys.length > maxKeys)
|
|
50
|
+
entries.push(['<truncated keys>', keys.length - maxKeys]);
|
|
51
|
+
return Object.fromEntries(entries);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
ancestors.delete(value);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
return visit(data, 0);
|
|
58
|
+
}
|
package/dist/domClicking.js
CHANGED
|
@@ -365,9 +365,22 @@ export function clickElementViaJS(expectedText) {
|
|
|
365
365
|
let hitTarget = null;
|
|
366
366
|
try {
|
|
367
367
|
hitTarget = document.elementFromPoint(cx, cy);
|
|
368
|
+
const visited = new Set();
|
|
369
|
+
while (hitTarget?.shadowRoot?.elementFromPoint && !visited.has(hitTarget)) {
|
|
370
|
+
visited.add(hitTarget);
|
|
371
|
+
const inner = hitTarget.shadowRoot.elementFromPoint(cx, cy);
|
|
372
|
+
if (!inner || inner === hitTarget)
|
|
373
|
+
break;
|
|
374
|
+
hitTarget = inner;
|
|
375
|
+
}
|
|
368
376
|
}
|
|
369
377
|
catch { }
|
|
378
|
+
let composedHit = hitTarget;
|
|
379
|
+
while (composedHit && composedHit !== target) {
|
|
380
|
+
composedHit = composedHit.parentElement || composedHit.getRootNode?.().host;
|
|
381
|
+
}
|
|
370
382
|
if (hitTarget &&
|
|
383
|
+
!composedHit &&
|
|
371
384
|
hitTarget !== target &&
|
|
372
385
|
!target.contains?.(hitTarget) &&
|
|
373
386
|
!hitTarget.contains?.(target)) {
|
|
@@ -680,9 +693,22 @@ export function clickElementBySelector(selector, invalidSelectorHint) {
|
|
|
680
693
|
let hitTarget = null;
|
|
681
694
|
try {
|
|
682
695
|
hitTarget = document.elementFromPoint(cx, cy);
|
|
696
|
+
const visited = new Set();
|
|
697
|
+
while (hitTarget?.shadowRoot?.elementFromPoint && !visited.has(hitTarget)) {
|
|
698
|
+
visited.add(hitTarget);
|
|
699
|
+
const inner = hitTarget.shadowRoot.elementFromPoint(cx, cy);
|
|
700
|
+
if (!inner || inner === hitTarget)
|
|
701
|
+
break;
|
|
702
|
+
hitTarget = inner;
|
|
703
|
+
}
|
|
683
704
|
}
|
|
684
705
|
catch { }
|
|
706
|
+
let composedHit = hitTarget;
|
|
707
|
+
while (composedHit && composedHit !== target) {
|
|
708
|
+
composedHit = composedHit.parentElement || composedHit.getRootNode?.().host;
|
|
709
|
+
}
|
|
685
710
|
if (hitTarget &&
|
|
711
|
+
!composedHit &&
|
|
686
712
|
hitTarget !== target &&
|
|
687
713
|
!target.contains(hitTarget) &&
|
|
688
714
|
!hitTarget.contains(target)) {
|
|
@@ -1001,9 +1027,22 @@ export function getClickCoordsFromSelector(selector, invalidSelectorHint) {
|
|
|
1001
1027
|
let hitTarget = null;
|
|
1002
1028
|
try {
|
|
1003
1029
|
hitTarget = document.elementFromPoint(x, y);
|
|
1030
|
+
const visited = new Set();
|
|
1031
|
+
while (hitTarget?.shadowRoot?.elementFromPoint && !visited.has(hitTarget)) {
|
|
1032
|
+
visited.add(hitTarget);
|
|
1033
|
+
const inner = hitTarget.shadowRoot.elementFromPoint(x, y);
|
|
1034
|
+
if (!inner || inner === hitTarget)
|
|
1035
|
+
break;
|
|
1036
|
+
hitTarget = inner;
|
|
1037
|
+
}
|
|
1004
1038
|
}
|
|
1005
1039
|
catch { }
|
|
1040
|
+
let composedHit = hitTarget;
|
|
1041
|
+
while (composedHit && composedHit !== target) {
|
|
1042
|
+
composedHit = composedHit.parentElement || composedHit.getRootNode?.().host;
|
|
1043
|
+
}
|
|
1006
1044
|
if (hitTarget &&
|
|
1045
|
+
!composedHit &&
|
|
1007
1046
|
hitTarget !== target &&
|
|
1008
1047
|
!target.contains(hitTarget) &&
|
|
1009
1048
|
!hitTarget.contains(target)) {
|
|
@@ -1295,9 +1334,22 @@ export function getClickCoordsFromNode(expectedText) {
|
|
|
1295
1334
|
let hitTarget = null;
|
|
1296
1335
|
try {
|
|
1297
1336
|
hitTarget = document.elementFromPoint(x, y);
|
|
1337
|
+
const visited = new Set();
|
|
1338
|
+
while (hitTarget?.shadowRoot?.elementFromPoint && !visited.has(hitTarget)) {
|
|
1339
|
+
visited.add(hitTarget);
|
|
1340
|
+
const inner = hitTarget.shadowRoot.elementFromPoint(x, y);
|
|
1341
|
+
if (!inner || inner === hitTarget)
|
|
1342
|
+
break;
|
|
1343
|
+
hitTarget = inner;
|
|
1344
|
+
}
|
|
1298
1345
|
}
|
|
1299
1346
|
catch { }
|
|
1347
|
+
let composedHit = hitTarget;
|
|
1348
|
+
while (composedHit && composedHit !== target) {
|
|
1349
|
+
composedHit = composedHit.parentElement || composedHit.getRootNode?.().host;
|
|
1350
|
+
}
|
|
1300
1351
|
if (hitTarget &&
|
|
1352
|
+
!composedHit &&
|
|
1301
1353
|
hitTarget !== target &&
|
|
1302
1354
|
!target.contains?.(hitTarget) &&
|
|
1303
1355
|
!hitTarget.contains?.(target)) {
|
package/dist/domInspection.js
CHANGED
|
@@ -211,9 +211,21 @@ export function inspectElementsInPage(selector, filter, styles, maxResults, inva
|
|
|
211
211
|
let hit = null;
|
|
212
212
|
try {
|
|
213
213
|
hit = visible ? (documentRef.elementFromPoint?.(x, y) ?? null) : null;
|
|
214
|
+
const visited = new Set();
|
|
215
|
+
while (hit?.shadowRoot?.elementFromPoint && !visited.has(hit)) {
|
|
216
|
+
visited.add(hit);
|
|
217
|
+
const inner = hit.shadowRoot.elementFromPoint(x, y);
|
|
218
|
+
if (!inner || inner === hit)
|
|
219
|
+
break;
|
|
220
|
+
hit = inner;
|
|
221
|
+
}
|
|
214
222
|
}
|
|
215
223
|
catch { }
|
|
216
|
-
|
|
224
|
+
let composedHit = hit;
|
|
225
|
+
while (composedHit && composedHit !== element) {
|
|
226
|
+
composedHit = composedHit.parentElement || composedHit.getRootNode?.().host;
|
|
227
|
+
}
|
|
228
|
+
const topmost = !!hit && (!!composedHit || element.contains?.(hit));
|
|
217
229
|
const target = hit
|
|
218
230
|
? {
|
|
219
231
|
selector: selectorFor(hit),
|
package/dist/index.d.ts
CHANGED
|
@@ -37,6 +37,7 @@ export * from './pluginPages.js';
|
|
|
37
37
|
export * from './pluginPageRuntime.js';
|
|
38
38
|
export * from './pluginPageHost.js';
|
|
39
39
|
export * from './browserActionProtocol.js';
|
|
40
|
+
export * from './browserCapabilities.js';
|
|
40
41
|
export * from './runActions.js';
|
|
41
42
|
export * from './sendRequestFiles.js';
|
|
42
43
|
export * from './sendRequestHeaders.js';
|
|
@@ -57,3 +58,4 @@ export * from './consumeSseResponse.js';
|
|
|
57
58
|
export { parseQueryString } from './queryString.js';
|
|
58
59
|
export * from './extensionVersion.js';
|
|
59
60
|
export { MEARL_NATIVE_HOST_NAMES, type NativeHostIntegrationMode } from './nativeHostNames.js';
|
|
61
|
+
export * from './dataStructure.js';
|
package/dist/index.js
CHANGED
|
@@ -36,6 +36,7 @@ export * from './pluginPages.js';
|
|
|
36
36
|
export * from './pluginPageRuntime.js';
|
|
37
37
|
export * from './pluginPageHost.js';
|
|
38
38
|
export * from './browserActionProtocol.js';
|
|
39
|
+
export * from './browserCapabilities.js';
|
|
39
40
|
export * from './runActions.js';
|
|
40
41
|
export * from './sendRequestFiles.js';
|
|
41
42
|
export * from './sendRequestHeaders.js';
|
|
@@ -56,3 +57,4 @@ export * from './consumeSseResponse.js';
|
|
|
56
57
|
export { parseQueryString } from './queryString.js';
|
|
57
58
|
export * from './extensionVersion.js';
|
|
58
59
|
export { MEARL_NATIVE_HOST_NAMES } from './nativeHostNames.js';
|
|
60
|
+
export * from './dataStructure.js';
|
package/dist/proxyPermissions.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Request serialization utilities — pure computation, no environment dependencies.
|
|
3
3
|
*/
|
|
4
|
+
import { getStructure } from './dataStructure.js';
|
|
4
5
|
// ─── Path utilities ───────────────────────────────────────────────────────────
|
|
5
6
|
function parsePath(path) {
|
|
6
7
|
if (path.length > 1000)
|
|
@@ -67,35 +68,6 @@ export function setValueByPath(obj, path, value) {
|
|
|
67
68
|
current[keys[keys.length - 1]] = value;
|
|
68
69
|
}
|
|
69
70
|
// ─── Structure helpers ────────────────────────────────────────────────────────
|
|
70
|
-
function getStructure(data, maxDepth = 5, currentDepth = 0) {
|
|
71
|
-
if (currentDepth >= maxDepth)
|
|
72
|
-
return '<max depth reached>';
|
|
73
|
-
if (data === null)
|
|
74
|
-
return 'null';
|
|
75
|
-
if (data === undefined)
|
|
76
|
-
return 'undefined';
|
|
77
|
-
const type = typeof data;
|
|
78
|
-
if (type === 'string' || type === 'number' || type === 'boolean')
|
|
79
|
-
return type;
|
|
80
|
-
if (Array.isArray(data)) {
|
|
81
|
-
if (data.length === 0)
|
|
82
|
-
return 'array[0]';
|
|
83
|
-
return {
|
|
84
|
-
_type: `array[${data.length}]`,
|
|
85
|
-
_sample: getStructure(data[0], maxDepth, currentDepth + 1),
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
if (type === 'object') {
|
|
89
|
-
const structure = {};
|
|
90
|
-
for (const key in data) {
|
|
91
|
-
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
92
|
-
structure[key] = getStructure(data[key], maxDepth, currentDepth + 1);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return structure;
|
|
96
|
-
}
|
|
97
|
-
return type;
|
|
98
|
-
}
|
|
99
71
|
function truncateData(data, maxSize) {
|
|
100
72
|
const str = JSON.stringify(data);
|
|
101
73
|
if (str.length <= maxSize)
|
package/dist/runActions.js
CHANGED
|
@@ -22,7 +22,7 @@ export async function runActionBatch(data, execute, options = {}) {
|
|
|
22
22
|
validationError = `unsupported action "${action}"`;
|
|
23
23
|
else if (action === 'run_actions')
|
|
24
24
|
validationError = `composite action "${action}" is not allowed in run_actions`;
|
|
25
|
-
else if (action === 'browser_release')
|
|
25
|
+
else if (action === 'browser_release' || action === 'get_capabilities')
|
|
26
26
|
validationError = `browser-level action "${action}" is not allowed in run_actions`;
|
|
27
27
|
if (validationError)
|
|
28
28
|
return {
|
package/package.json
CHANGED