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.
@@ -21,21 +21,34 @@ CDP 路由通过 `--remote-debugging-port` 暴露调试端口,**强风控脚
21
21
  **高危平台必须走 extension 路由**:`open({ routeMode: "extension" })`。
22
22
  若 profile 没装扩展,extension 路会失败——此时**停下来报告用户**,不要降级到 CDP 凑合。
23
23
 
24
- ## WebPlater 扩展安装(每个 profile 一次性)
24
+ ## WebPlater 扩展安装(每个实例 profile 一次性)
25
25
 
26
- extension 路由依赖 WebPlater 扩展。**扩展装在具体 Chrome profile 里**——换 profile 就要重装。
27
- 首次使用高危平台前,用户需在目标 profile WebPlater:
26
+ extension 路由依赖 WebPlater 扩展。**扩展装在具体实例的 Chrome profile 里**——
27
+ 每个实例有独立 profile(`config/browser.json` 里的 `profileDir`),换实例就要重装。
28
28
 
29
- 1. `mooncat-browser install-extension` 拿到扩展目录绝对路径(包内 `browser-op/webplater/dist/chrome-mv3/`)。
30
- 2. `mooncat-browser start --port 17322` 启动服务,`browser.open({ routeMode: "auto" })` 启动 Chrome,
31
- 拿到 profile 的 user-data-dir(默认 `%LOCALAPPDATA%\mooncat-browser\profile`,或 `--profile` 指定)。
32
- 3. 用户在该 Chrome 访问 `chrome://extensions`,开「开发者模式」。
33
- 4. 点「加载已解压的扩展程序」,选第 1 步拿到的扩展目录(校验路径下有 `manifest.json`)。
34
- 5. 等几秒 background service worker 连上 WS server。
35
- 6. 重新 `open`(routeMode=auto),看返回的 `mode` 是否为 `extension`。
29
+ 多实例下,**每个实例生成自己的扩展目录**(写死该实例的 WS 端口),扩展不共享:
36
30
 
37
- **必须先算好扩展绝对路径再告诉用户**,不要让用户自己找。`mooncat-browser install-extension`
38
- 会打印路径。校验路径下有 `manifest.json`。
31
+ ```bash
32
+ # 1. 为实例生成 unpacked 扩展目录(copy 包内模板 + 替换 WS 端口占位符)
33
+ npx mooncat-browser prepare-extension <实例名>
34
+ # 产物在 <实例 extensionDir>(默认 .browser/<实例名>/extension)
35
+ # 里面 offscreen 代码已写死连 ws://127.0.0.1:<该实例 extensionPort>
36
+
37
+ # 2. 启动实例(会弹出用该实例 profile 的 Chrome)
38
+ npx mooncat-browser start <实例名> --route-mode extension
39
+
40
+ # 3. 在弹出的 Chrome 里手动 load unpacked:
41
+ # chrome://extensions → 开「开发者模式」→「加载已解压的扩展程序」
42
+ # 选择:<实例 extensionDir>(校验路径下有 manifest.json)
43
+ # profile 会永久记住这次安装,以后 start 不用再 load
44
+ ```
45
+
46
+ `prepare-extension` 会打印实例 extensionDir 的绝对路径——**把这个路径告诉用户**,
47
+ 不要让用户自己找。校验路径下有 `manifest.json` 即正确。
48
+
49
+ > 注意:extension 路不能 headless(Chrome 限制)。高危平台本就要看得见浏览器,不冲突。
50
+ > 改了实例 extensionPort 或升级了包(扩展 build 变了),要重 `prepare-extension` 并在
51
+ > Chrome 里重新 load unpacked。
39
52
 
40
53
  ## 装了扩展后必须重启浏览器
41
54
 
@@ -36,6 +36,7 @@ iframe/portal/shadow 里的元素。正确做法是上面 5 类系统性枚举,
36
36
 
37
37
  ```ts
38
38
  import { BrowserClient } from "@mooncat/browser";
39
+ // 端口 = 实例 rpcPort(默认 17322,多实例各自不同,看 config/browser.json)
39
40
  const browser = new BrowserClient({ baseUrl: "http://127.0.0.1:17322" });
40
41
 
41
42
  const tab = await browser.newTab({ url: "https://example.com" });
@@ -261,6 +262,7 @@ const browser = new BrowserClient({ baseUrl: "http://127.0.0.1:17322" });
261
262
 
262
263
  const done = probe.section("采集订单"); // 结构化段落
263
264
 
265
+ // 高危平台:open({ routeMode: "extension" }),见 high-risk.md
264
266
  await browser.open({ headless: false });
265
267
  const tab = await browser.newTab({ url: "https://example.com/orders" });
266
268
  const page = tab.pageHandle;
@@ -1 +0,0 @@
1
- import"./_virtual_wxt-html-plugins-DPbbfBKe.js";const l="ws://127.0.0.1:17321",p=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"}),y()}),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 S(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(),b()}),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 S(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 y(){f(),c=setInterval(()=>{i({type:"ping",time:Date.now()})},E)}function f(){c&&(clearInterval(c),c=null)}function b(){s||(s=setTimeout(()=>{s=null,d()},p))}d();