@mearl/provider 3.0.0-beta.0 → 3.0.0-beta.10
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 +147 -102
- package/README.zh-CN.md +248 -0
- package/dist/app-resources.d.ts +15 -0
- package/dist/app-resources.js +55 -0
- package/dist/control-host.d.ts +10 -0
- package/dist/control-host.js +5 -0
- package/dist/control-rpc.d.ts +7 -0
- package/dist/control-rpc.js +66 -0
- package/dist/enhancements.d.ts +19 -0
- package/dist/enhancements.js +100 -0
- package/dist/host.d.ts +1 -1
- package/dist/host.js +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/launcher.js +18 -7
- package/dist/protocol.d.ts +2 -0
- package/dist/protocol.js +5 -0
- package/dist/provider-runtime.js +67 -1
- package/dist/registry.js +3 -1
- package/dist/server.js +44 -6
- package/dist/types.d.ts +16 -3
- package/package.json +9 -4
package/dist/server.js
CHANGED
|
@@ -4,12 +4,19 @@ import { chmodSync, rmSync } from 'node:fs';
|
|
|
4
4
|
import { dirname } from 'node:path';
|
|
5
5
|
import { ensureDir } from '@mearl/daemon-core';
|
|
6
6
|
import { isProviderAction } from './actions.js';
|
|
7
|
-
import { createProviderRuntime } from './provider-runtime.js';
|
|
7
|
+
import { createProviderRuntime, validateProviderBrowser } from './provider-runtime.js';
|
|
8
8
|
import { PROVIDER_START_TOKEN_ENV, claimProviderStart, providerBrowserKey, providerSocketPath, publishLiveProvider, publishProviderBrowsers, readLiveProvider, releaseProviderStart, removeStaleProviderSocket, touchProviderBrowser, unpublishLiveProvider, unpublishProviderBrowsers, } from './registry.js';
|
|
9
9
|
import { MEARL_PROVIDER_PROTOCOL_VERSION } from './types.js';
|
|
10
10
|
import { assertProviderProtocolVersion } from './protocol.js';
|
|
11
|
+
import { assertProviderControlSessionMode, ensureProviderControlHost, readProviderControlHost } from './control-host.js';
|
|
11
12
|
const MAX_REQUEST_BYTES = 16 * 1024 * 1024;
|
|
12
13
|
const activeProviderIds = new Set();
|
|
14
|
+
function assertControlHost(browser) {
|
|
15
|
+
validateProviderBrowser(browser);
|
|
16
|
+
if (browser.control.kind === 'provider' && browser.control.capabilities.controlSessionMode === 'external') {
|
|
17
|
+
assertProviderControlSessionMode(readProviderControlHost(), 'external');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
13
20
|
function errorMessage(error) {
|
|
14
21
|
return error instanceof Error ? error.message : String(error);
|
|
15
22
|
}
|
|
@@ -31,6 +38,7 @@ function isProviderActionRequest(value) {
|
|
|
31
38
|
typeof request.action === 'string' &&
|
|
32
39
|
request.action.length > 0 &&
|
|
33
40
|
typeof request.protocolVersion === 'number' &&
|
|
41
|
+
(request.deadline === undefined || (Number.isSafeInteger(request.deadline) && request.deadline > 0)) &&
|
|
34
42
|
(request.data === undefined ||
|
|
35
43
|
(request.data !== null &&
|
|
36
44
|
typeof request.data === 'object' &&
|
|
@@ -44,6 +52,7 @@ function providerContext(request) {
|
|
|
44
52
|
const hostData = request.host;
|
|
45
53
|
return {
|
|
46
54
|
controlSource: request.controlSource,
|
|
55
|
+
...(request.deadline !== undefined ? { deadline: request.deadline } : {}),
|
|
47
56
|
...(hostData
|
|
48
57
|
? {
|
|
49
58
|
host: {
|
|
@@ -65,6 +74,7 @@ function providerContext(request) {
|
|
|
65
74
|
export function createProviderServer(options) {
|
|
66
75
|
const runtime = createProviderRuntime(options.definition);
|
|
67
76
|
const descriptor = options.definition.descriptor;
|
|
77
|
+
const requiresExternalControl = options.definition.browserType.controlSessionModes?.includes('external') === true;
|
|
68
78
|
const socketPath = options.socketPath ?? providerSocketPath(descriptor.providerId);
|
|
69
79
|
let server = null;
|
|
70
80
|
let browserIds = new Set();
|
|
@@ -72,20 +82,43 @@ export function createProviderServer(options) {
|
|
|
72
82
|
let state = 'idle';
|
|
73
83
|
let stopPromise = null;
|
|
74
84
|
const connections = new Set();
|
|
85
|
+
async function prepareControlHost() {
|
|
86
|
+
if (requiresExternalControl) {
|
|
87
|
+
assertProviderControlSessionMode(await ensureProviderControlHost(), 'external');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
75
90
|
async function providerBrowserId(requested) {
|
|
76
|
-
if (requested)
|
|
77
|
-
return requested;
|
|
78
91
|
const available = await runtime.listBrowsers();
|
|
79
|
-
|
|
80
|
-
|
|
92
|
+
const selected = requested
|
|
93
|
+
? available.find(browser => browser.browserId === requested)
|
|
94
|
+
: available.length === 1 ? available[0] : undefined;
|
|
95
|
+
if (selected) {
|
|
96
|
+
assertControlHost(selected);
|
|
97
|
+
return selected.browserId;
|
|
98
|
+
}
|
|
99
|
+
if (requested)
|
|
100
|
+
throw new Error(`Provider browser not found: ${requested}`);
|
|
81
101
|
if (available.length === 0)
|
|
82
102
|
throw new Error('Provider has no browser instances');
|
|
83
103
|
throw new Error('browserId is required when the provider has multiple browsers');
|
|
84
104
|
}
|
|
85
105
|
async function dispatch(request) {
|
|
106
|
+
const assertDeadline = () => {
|
|
107
|
+
if (request.deadline !== undefined && Date.now() >= request.deadline)
|
|
108
|
+
throw new Error('Provider request deadline exceeded');
|
|
109
|
+
};
|
|
110
|
+
assertDeadline();
|
|
86
111
|
if (request.controlSessionId)
|
|
87
112
|
throw new Error('CONTROL_SESSION_UNSUPPORTED: this Provider does not implement control sessions');
|
|
113
|
+
await prepareControlHost();
|
|
114
|
+
assertDeadline();
|
|
88
115
|
const context = providerContext(request);
|
|
116
|
+
if (request.action === '__provider_enhancement_target') {
|
|
117
|
+
const browserId = await providerBrowserId(request.browserId);
|
|
118
|
+
if (!runtime.getEnhancementTarget)
|
|
119
|
+
throw new Error('BROWSER_ENHANCEMENT_TARGET_UNSUPPORTED');
|
|
120
|
+
return runtime.getEnhancementTarget({ browserId, tabId: request.data?.tabId }, context);
|
|
121
|
+
}
|
|
89
122
|
if (request.action === 'browser_providers') {
|
|
90
123
|
if (request.browserId)
|
|
91
124
|
throw new Error('Provider management does not accept browserId');
|
|
@@ -104,7 +137,9 @@ export function createProviderServer(options) {
|
|
|
104
137
|
if (request.action === 'browser_launch') {
|
|
105
138
|
if (request.browserId)
|
|
106
139
|
throw new Error('browser_launch does not accept browserId');
|
|
107
|
-
|
|
140
|
+
const browser = await runtime.launch(request.data ?? {}, context);
|
|
141
|
+
assertControlHost(browser);
|
|
142
|
+
return browser;
|
|
108
143
|
}
|
|
109
144
|
if (request.action === 'browser_instances') {
|
|
110
145
|
if (!['start', 'detach', 'sync_cookies'].includes(String(request.data?.operation))) {
|
|
@@ -156,6 +191,7 @@ export function createProviderServer(options) {
|
|
|
156
191
|
action: request.action,
|
|
157
192
|
data: request.data ?? {},
|
|
158
193
|
controlSource: request.controlSource,
|
|
194
|
+
...(request.deadline !== undefined ? { deadline: request.deadline } : {}),
|
|
159
195
|
});
|
|
160
196
|
}
|
|
161
197
|
function handleConnection(socket) {
|
|
@@ -229,6 +265,7 @@ export function createProviderServer(options) {
|
|
|
229
265
|
throw new Error(`Provider ${providerId} start is already in progress`);
|
|
230
266
|
}
|
|
231
267
|
try {
|
|
268
|
+
await prepareControlHost();
|
|
232
269
|
const competing = readLiveProvider(providerId);
|
|
233
270
|
if (competing) {
|
|
234
271
|
throw new Error(`Provider ${providerId} is already running (pid ${competing.pid})`);
|
|
@@ -271,6 +308,7 @@ export function createProviderServer(options) {
|
|
|
271
308
|
updateBrowsers(nextBrowsers) {
|
|
272
309
|
if (state !== 'running')
|
|
273
310
|
throw new Error('Provider server is not running');
|
|
311
|
+
nextBrowsers.forEach(assertControlHost);
|
|
274
312
|
runtime.updateBrowsers(nextBrowsers);
|
|
275
313
|
browsers = [...nextBrowsers];
|
|
276
314
|
browserIds = publishProviderBrowsers(descriptor, socketPath, browsers, browserIds);
|
package/dist/types.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import type { ProviderAction } from './actions.js';
|
|
2
|
+
import type { BrowserCapabilities, BrowserControlSessionMode, ProviderEnhancementTarget } from '@mearl/browser-core';
|
|
2
3
|
export { MEARL_PROVIDER_PROTOCOL_VERSION } from './protocol.js';
|
|
3
4
|
export type { ProviderAction } from './actions.js';
|
|
5
|
+
export type { BrowserEnhancementInvocation, ProviderEnhancementBinding, ProviderEnhancementTarget } from '@mearl/browser-core';
|
|
4
6
|
export type JsonPrimitive = string | number | boolean | null;
|
|
5
7
|
export type JsonValue = JsonPrimitive | JsonValue[] | JsonObject;
|
|
6
8
|
export interface JsonObject {
|
|
7
9
|
[key: string]: JsonValue;
|
|
8
10
|
}
|
|
9
11
|
export type ProviderScreenshotKind = 'native' | 'reconstructed' | 'none';
|
|
10
|
-
export interface ProviderCapabilities {
|
|
11
|
-
actions: ProviderAction[];
|
|
12
|
-
screenshot: ProviderScreenshotKind;
|
|
12
|
+
export interface ProviderCapabilities extends BrowserCapabilities {
|
|
13
13
|
}
|
|
14
14
|
/** Completed download staged on the local Provider process host for Mearl transfer. */
|
|
15
15
|
export interface ProviderDownloadResult {
|
|
@@ -123,6 +123,8 @@ export interface ProviderConfigurationDefinition {
|
|
|
123
123
|
}
|
|
124
124
|
export interface ProviderBrowserType {
|
|
125
125
|
name: string;
|
|
126
|
+
/** Host must support every listed mode before launch. Omitted means Mearl only. */
|
|
127
|
+
controlSessionModes?: readonly BrowserControlSessionMode[];
|
|
126
128
|
documentationUrl?: string;
|
|
127
129
|
/** Provider-owned usage guidance rendered by `mearl <providerId>`. */
|
|
128
130
|
help?: readonly string[];
|
|
@@ -198,6 +200,8 @@ export interface ProviderActionRequest {
|
|
|
198
200
|
browserId?: string;
|
|
199
201
|
version?: string;
|
|
200
202
|
protocolVersion: number;
|
|
203
|
+
/** Absolute caller deadline, outside Provider business options. */
|
|
204
|
+
deadline?: number;
|
|
201
205
|
controlSource?: string;
|
|
202
206
|
host?: ProviderHostContextData;
|
|
203
207
|
}
|
|
@@ -206,6 +210,7 @@ export interface ProviderActionContext {
|
|
|
206
210
|
action: ProviderAction;
|
|
207
211
|
data: JsonObject;
|
|
208
212
|
controlSource?: string;
|
|
213
|
+
deadline?: number;
|
|
209
214
|
}
|
|
210
215
|
export interface ProviderHostServices {
|
|
211
216
|
request<T = JsonValue>(method: string, data?: JsonObject): Promise<T>;
|
|
@@ -218,6 +223,7 @@ export interface ProviderHostContextData {
|
|
|
218
223
|
export interface ProviderContext {
|
|
219
224
|
host?: ProviderHostServices;
|
|
220
225
|
controlSource?: string;
|
|
226
|
+
deadline?: number;
|
|
221
227
|
}
|
|
222
228
|
export interface ProviderInstanceRequest {
|
|
223
229
|
browserId: string;
|
|
@@ -245,6 +251,10 @@ export interface BrowserProviderDefinition {
|
|
|
245
251
|
getAccess?: (request: ProviderInstanceRequest, context: ProviderContext) => Promise<ProviderAccess>;
|
|
246
252
|
configure?: (options: JsonObject, context: ProviderContext) => Promise<unknown>;
|
|
247
253
|
getLaunchForm?: (request: ProviderLaunchFormRequest, context: ProviderContext) => Promise<ProviderLaunchForm>;
|
|
254
|
+
/** Reentrant read-only preflight: must reflect current control and identity without claiming a page. */
|
|
255
|
+
getEnhancementTarget?: (request: ProviderInstanceRequest & {
|
|
256
|
+
tabId: number;
|
|
257
|
+
}, context: ProviderContext) => Promise<ProviderEnhancementTarget>;
|
|
248
258
|
handleAction?: (context: ProviderActionContext) => Promise<unknown>;
|
|
249
259
|
}
|
|
250
260
|
export interface ProviderRuntime {
|
|
@@ -260,6 +270,9 @@ export interface ProviderRuntime {
|
|
|
260
270
|
configure(options: JsonObject, context?: ProviderContext): Promise<unknown>;
|
|
261
271
|
getLaunchForm(request: ProviderLaunchFormRequest, context?: ProviderContext): Promise<ProviderLaunchForm>;
|
|
262
272
|
invokeAction(context: ProviderActionContext): Promise<unknown>;
|
|
273
|
+
getEnhancementTarget?(request: ProviderInstanceRequest & {
|
|
274
|
+
tabId: number;
|
|
275
|
+
}, context?: ProviderContext): Promise<ProviderEnhancementTarget>;
|
|
263
276
|
}
|
|
264
277
|
export interface ProviderServerOptions {
|
|
265
278
|
definition: BrowserProviderDefinition;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearl/provider",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.10",
|
|
4
4
|
"description": "Unified contracts and runtime utilities for built-in and external Mearl browser providers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,19 +29,24 @@
|
|
|
29
29
|
],
|
|
30
30
|
"license": "Apache-2.0",
|
|
31
31
|
"engines": {
|
|
32
|
-
"node": ">=
|
|
32
|
+
"node": ">=20.18.0"
|
|
33
33
|
},
|
|
34
34
|
"publishConfig": {
|
|
35
35
|
"registry": "https://registry.npmjs.org"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@mearl/daemon-core": "3.0.0-beta.
|
|
39
|
-
"@mearl/browser-core": "3.0.0-beta.
|
|
38
|
+
"@mearl/daemon-core": "3.0.0-beta.10",
|
|
39
|
+
"@mearl/browser-core": "3.0.0-beta.10"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^24.9.1",
|
|
43
43
|
"typescript": "^5.8.3"
|
|
44
44
|
},
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/mearlhq/mearl.git",
|
|
48
|
+
"directory": "packages/provider"
|
|
49
|
+
},
|
|
45
50
|
"scripts": {
|
|
46
51
|
"build": "rm -rf dist/ && tsc",
|
|
47
52
|
"typecheck": "tsc --noEmit",
|