@mearl/provider 3.0.0-beta.1 → 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/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
- if (available.length === 1)
80
- return available[0].browserId;
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
- return runtime.launch(request.data ?? {}, context);
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.1",
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,14 +29,14 @@
29
29
  ],
30
30
  "license": "Apache-2.0",
31
31
  "engines": {
32
- "node": ">=22.12.0"
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.1",
39
- "@mearl/browser-core": "3.0.0-beta.1"
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",
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "repository": {
46
46
  "type": "git",
47
- "url": "git+https://github.com/F-loat/mearl.git",
47
+ "url": "git+https://github.com/mearlhq/mearl.git",
48
48
  "directory": "packages/provider"
49
49
  },
50
50
  "scripts": {