mooncat-browser 0.1.0 → 0.2.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.
@@ -62,7 +62,10 @@ function getDefaultOp (options = {}) {
62
62
  }
63
63
  const profileRoot = options.profileRoot || process.env.BROWSER_OP_PROFILE_ROOT || getDefaultProfileRoot()
64
64
  const userDataDir = options.userDataDir || process.env.BROWSER_OP_USERDATA || null
65
- _op = new BrowserOp({ executablePath, profileRoot, userDataDir })
65
+ // 多实例 WS 端口:由 wrapper (mooncat-browser) 通过 env 注入本实例的 extensionPort。
66
+ // 不传则 BrowserOp/WebService 内部回退默认 17321(向后兼容)。
67
+ const wsPort = options.wsPort || (process.env.BROWSER_OP_WS_PORT ? Number(process.env.BROWSER_OP_WS_PORT) : null)
68
+ _op = new BrowserOp({ executablePath, profileRoot, userDataDir, wsPort })
66
69
  return _op
67
70
  }
68
71
 
@@ -59,10 +59,11 @@ class WebPluginServer {
59
59
 
60
60
  this.wss.on('error', (err) => {
61
61
  if (err.message.includes('EADDRINUSE')) {
62
- // 必须失败: 17321 被占说明已有 browserd 在管插件, 不能两个同时管
62
+ // 本端口被占:可能是本实例残留进程,或别的进程占用该端口。
63
+ // 多实例下每个实例有独立 WS 端口,不再有"全局唯一 17321"语义。
63
64
  this.info('[web] port %d already in use — EADDRINUSE', port)
64
65
  this.wss = null
65
- reject(new Error(`EADDRINUSE: port ${port} already in use (another browserd owns it)`))
66
+ reject(new Error(`EADDRINUSE: port ${port} already in use (stale process on this port, or another process occupied it)`))
66
67
  return
67
68
  }
68
69
  this.info('[web] server error: %s', err.message)
@@ -45,8 +45,13 @@ class WebService {
45
45
 
46
46
  // ── 生命周期 ──────────────────────────────────────────────
47
47
 
48
- async startServer () {
49
- await this.server.start()
48
+ /**
49
+ * 启动 WS server(供 WebPlater 扩展 offscreen 回连)
50
+ * @param {number=} port 实例 WS 端口;不传则用 server 默认(17321,仅向后兼容)。
51
+ * 多实例下由 index.cjs 从 env BROWSER_OP_WS_PORT 透传。
52
+ */
53
+ async startServer (port) {
54
+ await this.server.start(undefined, port)
50
55
  }
51
56
 
52
57
  async stopServer () {
@@ -49,6 +49,7 @@ class BrowserOp {
49
49
  * @param {string} deps.executablePath Chrome 路径(必填)
50
50
  * @param {string} deps.profileRoot profile 根目录(检测插件用,可选)
51
51
  * @param {string=} deps.userDataDir 显式 userDataDir(不传则 launcher 用 profileRoot/default)
52
+ * @param {number=} deps.wsPort 插件路 WS server 端口(多实例化;不传则用默认 17321)
52
53
  * @param {Function=} deps.info 日志函数
53
54
  * @param {Function=} deps.errorLog 错误日志函数
54
55
  */
@@ -59,6 +60,9 @@ class BrowserOp {
59
60
  this.executablePath = deps.executablePath
60
61
  this.profileRoot = deps.profileRoot || null
61
62
  this.userDataDirOverride = deps.userDataDir || null
63
+ // 多实例:每个 browserd 在自己的 wsPort 上起 WS server,扩展 offscreen 连本实例端口。
64
+ // 不传时回退默认 17321(向后兼容旧调用方)。
65
+ this.wsPort = deps.wsPort || null
62
66
  this.info = deps.info || defaultInfo
63
67
  this.errorLog = deps.errorLog || defaultError
64
68
 
@@ -109,15 +113,17 @@ class BrowserOp {
109
113
  let pluginFallbackNotice = null
110
114
 
111
115
  // 启动前:开插件 WS server(插件链路需要;CDP 链路开起来也不碍事,便于 checkRoute)
112
- // startServer 现在 EADDRINUSE reject: 17321 被占 = 已有 browserd 在管插件
116
+ // 多实例:用本实例 wsPort(从 env BROWSER_OP_WS_PORT browserd getDefaultOp BrowserOp 注入)。
117
+ // 不传则 web.startServer() 内部回退默认 17321。
113
118
  if (!this._serverStarted) {
114
119
  try {
115
- await this.web.startServer()
120
+ await this.web.startServer(this.wsPort)
116
121
  this._serverStarted = true
117
122
  } catch (e) {
118
123
  const portBusy = /EADDRINUSE|already in use/.test(e.message)
124
+ const portLabel = this.wsPort || 'default(17321)'
119
125
  const message = portBusy
120
- ? 'WebPlater WS 端口 17321 已被占用(另一个 browserd 正在管理插件,或残留进程)。'
126
+ ? `WebPlater WS 端口 ${portLabel} 已被占用(本实例残留进程,或别的进程占用了该端口)。`
121
127
  : `WS server 启动失败: ${e.message}`
122
128
  if (routeMode === 'extension') {
123
129
  return { ok: false, mode: 'extension', notice: { level: 'error', message }, error: message }
@@ -159,19 +165,10 @@ class BrowserOp {
159
165
 
160
166
  // tryPlugin=true → 插件模式启动(无端口)
161
167
  if (tryPlugin) {
162
- // 扩展路 handshake-first: startServer 后先等已有 Chrome 的 offscreen 回连 (2s)。
163
- // 已有 Chrome 开着 (别的进程起的/上次没关干净) offscreen 自动重连当前 17321 → 复用, 不 launch。
164
- // 没回连 → Chrome 没开或坏了 → 才 launch。
165
- const reused = await this.waitForPluginHeartbeat(2000)
166
- if (reused) {
167
- this.info('open mode=extension (reused: plugin already connected, skip launch)')
168
- this.currentMode = 'extension'
169
- extState.write(userDataDir, { wsPort: extState.DEFAULT_WS_PORT, lastHeartbeatAt: Date.now() })
170
- globalState.writeGlobal(userDataDir, { profile: 'default' })
171
- return { ok: true, mode: 'extension', notice: { level: 'info', message: '检测到该 profile 的 Chrome 已开且插件已连接,复用,未重复启动。' } }
172
- }
173
-
174
- // 没回连 → Chrome 没开, 正常 launch
168
+ // 多实例:本实例在自己的 wsPort 上起 WS server,扩展 offscreen 连本端口。
169
+ // 不再做"先等 2s 看有没有别的 browserd Chrome offscreen 回连"的复用分支——
170
+ // 那基于"17321 全局唯一"假设,多实例下每个实例端口独立,该假设不成立。
171
+ // 直接 launch 本实例的 Chrome(profile 独立),launch 后等自己的 offscreen 回连。
175
172
  const result = await this.launcher.launch({
176
173
  executablePath: this.executablePath,
177
174
  userDataDir,
@@ -191,7 +188,7 @@ class BrowserOp {
191
188
  if (alive) {
192
189
  this.info('open mode=extension (plugin alive)')
193
190
  this.currentMode = 'extension'
194
- extState.write(userDataDir, { wsPort: extState.DEFAULT_WS_PORT, lastHeartbeatAt: Date.now() })
191
+ extState.write(userDataDir, { wsPort: this.wsPort || extState.DEFAULT_WS_PORT, lastHeartbeatAt: Date.now() })
195
192
  globalState.writeGlobal(userDataDir, { profile: 'default' })
196
193
  return { ok: true, mode: 'extension', pid: this.launcher.processId, notice: null }
197
194
  }
@@ -0,0 +1 @@
1
+ import"./_virtual_wxt-html-plugins-DPbbfBKe.js";const l="__MOONCAT_BROWSER_HOST_WS_URL__",S=1e3,E=2e4;let e=null,s=null,c=null;function d(){(e==null?void 0:e.readyState)===WebSocket.CONNECTING||(e==null?void 0:e.readyState)===WebSocket.OPEN||(e=new WebSocket(l),e.addEventListener("open",()=>{s&&(clearTimeout(s),s=null),console.log("[WebPlater/offscreen] connected to host",l),i({type:"hello",name:"bee-web-plugin"}),_()}),e.addEventListener("message",async t=>{let n;try{n=JSON.parse(String(t.data))}catch{console.error("[WebPlater/offscreen] invalid JSON from host",t.data);return}if(!(!n||n.type!=="command"||typeof n.id!="number"))try{const r=await p(n.id,n.method,n.params??{});u(n.id,{ok:!0,result:r})}catch(r){u(n.id,{ok:!1,error:r instanceof Error?r.message:String(r)})}}),e.addEventListener("close",()=>{console.log("[WebPlater/offscreen] disconnected, reconnecting..."),e=null,f(),y()}),e.addEventListener("error",()=>{e==null||e.close()}))}function i(t){(e==null?void 0:e.readyState)===WebSocket.OPEN&&e.send(JSON.stringify(t))}function u(t,n){const r={id:t,type:"response",...n};i(r)}function p(t,n,r){return new Promise((m,a)=>{chrome.runtime.sendMessage({source:"offscreen-command",id:t,method:n,params:r},g=>{if(chrome.runtime.lastError){a(new Error(chrome.runtime.lastError.message));return}const o=g;if(o&&typeof o=="object"&&o.ok===!1){a(new Error(o.error??"background error"));return}m(o==null?void 0:o.result)})})}function _(){f(),c=setInterval(()=>{i({type:"ping",time:Date.now()})},E)}function f(){c&&(clearInterval(c),c=null)}function y(){s||(s=setTimeout(()=>{s=null,d()},S))}d();
@@ -1 +1 @@
1
- {"manifest_version":3,"name":"WebPlater","description":"Generic browser automation bridge — Playwright alternative layer","version":"0.0.1.2607032021","key":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlljeBdeINKB8ouCR6euqG5iegaoSZY3c/uubW1AJoJzoVNyiB8UPzpL98zfWXW5nMF+KTdGa1Vb19sBACMVxO8gXEbxS/gobfqJHddfe0qZpNDEBrM7lwVJtocPb9lYJK3rHASx2VSkRFRPoymaaNDaeEO/uiu9uCG2dut/V00UvUAaeMDM9bwiWxD8aJ5IINRFIk+UniZsPB0kY3tbcmqayGYsjYpwZmU9RXoLSBZjWa8CfFjTNeyPBtLpkj2groQN/Ytk1RWf5AEwAPIahfz/hn2kDOnL1OSNGyQfswL8GuWJZs9h2bvDQMKwCRUQ/iUuqypNQfbin42rpD1kz9QIDAQAB","permissions":["tabs","scripting","cookies","storage","offscreen","webNavigation","downloads"],"host_permissions":["<all_urls>"],"background":{"service_worker":"background.js"},"action":{"default_title":"WebPlater","default_popup":"popup.html"},"content_scripts":[{"matches":["<all_urls>"],"all_frames":true,"run_at":"document_idle","js":["content-scripts/content.js"]}]}
1
+ {"manifest_version":3,"name":"WebPlater","description":"Generic browser automation bridge — Playwright alternative layer","version":"0.0.1.2607041709","key":"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlljeBdeINKB8ouCR6euqG5iegaoSZY3c/uubW1AJoJzoVNyiB8UPzpL98zfWXW5nMF+KTdGa1Vb19sBACMVxO8gXEbxS/gobfqJHddfe0qZpNDEBrM7lwVJtocPb9lYJK3rHASx2VSkRFRPoymaaNDaeEO/uiu9uCG2dut/V00UvUAaeMDM9bwiWxD8aJ5IINRFIk+UniZsPB0kY3tbcmqayGYsjYpwZmU9RXoLSBZjWa8CfFjTNeyPBtLpkj2groQN/Ytk1RWf5AEwAPIahfz/hn2kDOnL1OSNGyQfswL8GuWJZs9h2bvDQMKwCRUQ/iUuqypNQfbin42rpD1kz9QIDAQAB","permissions":["tabs","scripting","cookies","storage","offscreen","webNavigation","downloads"],"host_permissions":["<all_urls>"],"background":{"service_worker":"background.js"},"action":{"default_title":"WebPlater","default_popup":"popup.html"},"content_scripts":[{"matches":["<all_urls>"],"all_frames":true,"run_at":"document_idle","js":["content-scripts/content.js"]}]}
@@ -3,7 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <title>WebPlater Offscreen</title>
6
- <script type="module" crossorigin src="/chunks/offscreen-CFXYw9Mo.js"></script>
6
+ <script type="module" crossorigin src="/chunks/offscreen-JoFB8k4y.js"></script>
7
7
  <link rel="modulepreload" crossorigin href="/chunks/_virtual_wxt-html-plugins-DPbbfBKe.js">
8
8
  </head>
9
9
  <body>
@@ -34,7 +34,10 @@ interface BackgroundResponse {
34
34
  error?: string
35
35
  }
36
36
 
37
- const HOST_WS_URL = 'ws://127.0.0.1:17321'
37
+ // 占位符:prepare-extension <name> 时会被替换为 ws://127.0.0.1:<extensionPort>。
38
+ // wxt build 保留字符串字面量(minification 只改变量名,不改字符串内容)。
39
+ // 包内 dist 只 build 一次,每个实例复制后由 mooncat-browser prepare-extension 换端口。
40
+ const HOST_WS_URL = '__MOONCAT_BROWSER_HOST_WS_URL__'
38
41
  const RECONNECT_DELAY_MS = 1000
39
42
  const PING_INTERVAL_MS = 20000
40
43
 
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,3 @@
1
+ #!/usr/bin/env node
1
2
  export {};
2
3
  //# sourceMappingURL=cli.d.ts.map