@godscene/web 1.7.11

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.
Files changed (129) hide show
  1. package/README.md +7 -0
  2. package/bin/midscene-playground +3 -0
  3. package/bin/midscene-web +2 -0
  4. package/dist/es/bin.mjs +14 -0
  5. package/dist/es/bridge-mode/agent-cli-side.mjs +135 -0
  6. package/dist/es/bridge-mode/browser.mjs +2 -0
  7. package/dist/es/bridge-mode/common.mjs +41 -0
  8. package/dist/es/bridge-mode/index.mjs +4 -0
  9. package/dist/es/bridge-mode/io-client.mjs +99 -0
  10. package/dist/es/bridge-mode/io-server.mjs +218 -0
  11. package/dist/es/bridge-mode/page-browser-side.mjs +119 -0
  12. package/dist/es/cdp-proxy-constants.mjs +7 -0
  13. package/dist/es/cdp-proxy-manager.mjs +217 -0
  14. package/dist/es/cdp-proxy.mjs +151 -0
  15. package/dist/es/cdp-target-store.mjs +26 -0
  16. package/dist/es/chrome-extension/agent.mjs +8 -0
  17. package/dist/es/chrome-extension/cdpInput.mjs +172 -0
  18. package/dist/es/chrome-extension/cdpInput.mjs.LICENSE.txt +5 -0
  19. package/dist/es/chrome-extension/dynamic-scripts.mjs +36 -0
  20. package/dist/es/chrome-extension/index.mjs +5 -0
  21. package/dist/es/chrome-extension/page.mjs +733 -0
  22. package/dist/es/cli-options.mjs +97 -0
  23. package/dist/es/cli.mjs +26 -0
  24. package/dist/es/common/cache-helper.mjs +26 -0
  25. package/dist/es/common/viewport.mjs +36 -0
  26. package/dist/es/index.mjs +8 -0
  27. package/dist/es/mcp-server.mjs +33 -0
  28. package/dist/es/mcp-tools-cdp.mjs +164 -0
  29. package/dist/es/mcp-tools-puppeteer.mjs +246 -0
  30. package/dist/es/mcp-tools.mjs +81 -0
  31. package/dist/es/platform.mjs +37 -0
  32. package/dist/es/playwright/ai-fixture.mjs +364 -0
  33. package/dist/es/playwright/index.mjs +36 -0
  34. package/dist/es/playwright/page.mjs +42 -0
  35. package/dist/es/playwright/reporter/index.mjs +178 -0
  36. package/dist/es/puppeteer/agent-launcher.mjs +172 -0
  37. package/dist/es/puppeteer/base-page.mjs +830 -0
  38. package/dist/es/puppeteer/index.mjs +35 -0
  39. package/dist/es/puppeteer/page.mjs +7 -0
  40. package/dist/es/static/index.mjs +3 -0
  41. package/dist/es/static/static-agent.mjs +10 -0
  42. package/dist/es/static/static-page.mjs +123 -0
  43. package/dist/es/utils.mjs +6 -0
  44. package/dist/es/web-element.mjs +57 -0
  45. package/dist/es/web-page.mjs +272 -0
  46. package/dist/lib/bin.js +20 -0
  47. package/dist/lib/bridge-mode/agent-cli-side.js +172 -0
  48. package/dist/lib/bridge-mode/browser.js +36 -0
  49. package/dist/lib/bridge-mode/common.js +105 -0
  50. package/dist/lib/bridge-mode/index.js +44 -0
  51. package/dist/lib/bridge-mode/io-client.js +133 -0
  52. package/dist/lib/bridge-mode/io-server.js +255 -0
  53. package/dist/lib/bridge-mode/page-browser-side.js +163 -0
  54. package/dist/lib/cdp-proxy-constants.js +50 -0
  55. package/dist/lib/cdp-proxy-manager.js +273 -0
  56. package/dist/lib/cdp-proxy.js +179 -0
  57. package/dist/lib/cdp-target-store.js +66 -0
  58. package/dist/lib/chrome-extension/agent.js +42 -0
  59. package/dist/lib/chrome-extension/cdpInput.js +206 -0
  60. package/dist/lib/chrome-extension/cdpInput.js.LICENSE.txt +5 -0
  61. package/dist/lib/chrome-extension/dynamic-scripts.js +86 -0
  62. package/dist/lib/chrome-extension/index.js +58 -0
  63. package/dist/lib/chrome-extension/page.js +767 -0
  64. package/dist/lib/cli-options.js +131 -0
  65. package/dist/lib/cli.js +54 -0
  66. package/dist/lib/common/cache-helper.js +66 -0
  67. package/dist/lib/common/viewport.js +88 -0
  68. package/dist/lib/index.js +66 -0
  69. package/dist/lib/mcp-server.js +73 -0
  70. package/dist/lib/mcp-tools-cdp.js +208 -0
  71. package/dist/lib/mcp-tools-puppeteer.js +296 -0
  72. package/dist/lib/mcp-tools.js +115 -0
  73. package/dist/lib/platform.js +71 -0
  74. package/dist/lib/playwright/ai-fixture.js +401 -0
  75. package/dist/lib/playwright/index.js +89 -0
  76. package/dist/lib/playwright/page.js +76 -0
  77. package/dist/lib/playwright/reporter/index.js +212 -0
  78. package/dist/lib/puppeteer/agent-launcher.js +240 -0
  79. package/dist/lib/puppeteer/base-page.js +876 -0
  80. package/dist/lib/puppeteer/index.js +85 -0
  81. package/dist/lib/puppeteer/page.js +41 -0
  82. package/dist/lib/static/index.js +50 -0
  83. package/dist/lib/static/static-agent.js +44 -0
  84. package/dist/lib/static/static-page.js +157 -0
  85. package/dist/lib/utils.js +38 -0
  86. package/dist/lib/web-element.js +94 -0
  87. package/dist/lib/web-page.js +322 -0
  88. package/dist/types/bin.d.ts +1 -0
  89. package/dist/types/bridge-mode/agent-cli-side.d.ts +49 -0
  90. package/dist/types/bridge-mode/browser.d.ts +2 -0
  91. package/dist/types/bridge-mode/common.d.ts +74 -0
  92. package/dist/types/bridge-mode/index.d.ts +4 -0
  93. package/dist/types/bridge-mode/io-client.d.ts +10 -0
  94. package/dist/types/bridge-mode/io-server.d.ts +27 -0
  95. package/dist/types/bridge-mode/page-browser-side.d.ts +21 -0
  96. package/dist/types/cdp-proxy-constants.d.ts +4 -0
  97. package/dist/types/cdp-proxy-manager.d.ts +53 -0
  98. package/dist/types/cdp-proxy.d.ts +37 -0
  99. package/dist/types/cdp-target-store.d.ts +26 -0
  100. package/dist/types/chrome-extension/agent.d.ts +4 -0
  101. package/dist/types/chrome-extension/cdpInput.d.ts +52 -0
  102. package/dist/types/chrome-extension/dynamic-scripts.d.ts +3 -0
  103. package/dist/types/chrome-extension/index.d.ts +5 -0
  104. package/dist/types/chrome-extension/page.d.ts +120 -0
  105. package/dist/types/cli-options.d.ts +8 -0
  106. package/dist/types/cli.d.ts +1 -0
  107. package/dist/types/common/cache-helper.d.ts +20 -0
  108. package/dist/types/common/viewport.d.ts +17 -0
  109. package/dist/types/index.d.ts +9 -0
  110. package/dist/types/mcp-server.d.ts +26 -0
  111. package/dist/types/mcp-tools-cdp.d.ts +23 -0
  112. package/dist/types/mcp-tools-puppeteer.d.ts +23 -0
  113. package/dist/types/mcp-tools.d.ts +14 -0
  114. package/dist/types/platform.d.ts +10 -0
  115. package/dist/types/playwright/ai-fixture.d.ts +133 -0
  116. package/dist/types/playwright/index.d.ts +13 -0
  117. package/dist/types/playwright/page.d.ts +11 -0
  118. package/dist/types/playwright/reporter/index.d.ts +28 -0
  119. package/dist/types/puppeteer/agent-launcher.d.ts +59 -0
  120. package/dist/types/puppeteer/base-page.d.ts +123 -0
  121. package/dist/types/puppeteer/index.d.ts +11 -0
  122. package/dist/types/puppeteer/page.d.ts +6 -0
  123. package/dist/types/static/index.d.ts +2 -0
  124. package/dist/types/static/static-agent.d.ts +5 -0
  125. package/dist/types/static/static-page.d.ts +46 -0
  126. package/dist/types/utils.d.ts +6 -0
  127. package/dist/types/web-element.d.ts +48 -0
  128. package/dist/types/web-page.d.ts +69 -0
  129. package/package.json +173 -0
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # @midscene/web
2
+
3
+ Automate UI actions, extract data, and perform assertions using AI for web browsers.
4
+
5
+ - **Playwright integration**: <https://midscenejs.com/integrate-with-playwright.html>
6
+ - **Puppeteer integration**: <https://midscenejs.com/integrate-with-puppeteer.html>
7
+ - **Bridge mode**: <https://midscenejs.com/bridge-mode.html>
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('../dist/lib/bin.js');
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../dist/lib/cli.js');
@@ -0,0 +1,14 @@
1
+ import { launchPreparedPlaygroundPlatform } from "@godscene/playground";
2
+ import "dotenv/config";
3
+ import { webPlaygroundPlatform } from "./platform.mjs";
4
+ async function startServer() {
5
+ const prepared = await webPlaygroundPlatform.prepare({
6
+ launchOptions: {
7
+ openBrowser: false,
8
+ verbose: false
9
+ }
10
+ });
11
+ const { server } = await launchPreparedPlaygroundPlatform(prepared);
12
+ console.log(`Midscene playground server is running on http://localhost:${server.port}`);
13
+ }
14
+ startServer().catch(console.error);
@@ -0,0 +1,135 @@
1
+ import { Agent } from "@godscene/core/agent";
2
+ import { assert } from "@godscene/shared/utils";
3
+ import { commonWebActionsForWebPage } from "../web-page.mjs";
4
+ import { BridgeEvent, BridgePageType, DefaultBridgeServerHost, DefaultBridgeServerPort, KeyboardEvent, MouseEvent, getBridgeServerHost } from "./common.mjs";
5
+ import { BridgeServer } from "./io-server.mjs";
6
+ function _define_property(obj, key, value) {
7
+ if (key in obj) Object.defineProperty(obj, key, {
8
+ value: value,
9
+ enumerable: true,
10
+ configurable: true,
11
+ writable: true
12
+ });
13
+ else obj[key] = value;
14
+ return obj;
15
+ }
16
+ const sleep = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
17
+ const getBridgePageInCliSide = (options)=>{
18
+ const host = options?.host || DefaultBridgeServerHost;
19
+ const port = options?.port || DefaultBridgeServerPort;
20
+ const server = new BridgeServer(host, port, void 0, void 0, options?.closeConflictServer);
21
+ server.listen({
22
+ timeout: options?.timeout
23
+ });
24
+ const bridgeCaller = (method, timeout)=>async (...args)=>{
25
+ const response = await server.call(method, args, timeout);
26
+ return response;
27
+ };
28
+ const page = {
29
+ showStatusMessage: async (message)=>{
30
+ await server.call(BridgeEvent.UpdateAgentStatus, [
31
+ message
32
+ ]);
33
+ }
34
+ };
35
+ const proxyPage = new Proxy(page, {
36
+ get (target, prop, receiver) {
37
+ assert('string' == typeof prop, 'prop must be a string');
38
+ if ('toJSON' === prop) return ()=>({
39
+ interfaceType: BridgePageType
40
+ });
41
+ if ('interfaceType' === prop) return BridgePageType;
42
+ if ('actionSpace' === prop) return ()=>commonWebActionsForWebPage(proxyPage);
43
+ if (Object.keys(page).includes(prop)) return page[prop];
44
+ if ('mouse' === prop) {
45
+ const mouse = {
46
+ click: bridgeCaller(MouseEvent.Click),
47
+ wheel: bridgeCaller(MouseEvent.Wheel),
48
+ move: bridgeCaller(MouseEvent.Move),
49
+ drag: bridgeCaller(MouseEvent.Drag)
50
+ };
51
+ return mouse;
52
+ }
53
+ if ('keyboard' === prop) {
54
+ const keyboard = {
55
+ type: bridgeCaller(KeyboardEvent.Type),
56
+ press: bridgeCaller(KeyboardEvent.Press)
57
+ };
58
+ return keyboard;
59
+ }
60
+ if ('destroy' === prop) return async (...args)=>{
61
+ try {
62
+ const caller = bridgeCaller('destroy');
63
+ await caller(...args);
64
+ } catch (e) {}
65
+ return server.close();
66
+ };
67
+ if ('connectNewTabWithUrl' === prop) return async (url, options)=>{
68
+ const timeout = options?.timeout;
69
+ const caller = bridgeCaller(prop, timeout);
70
+ return await caller(url, options);
71
+ };
72
+ if ('connectCurrentTab' === prop) return async (options)=>{
73
+ const timeout = options?.timeout;
74
+ const caller = bridgeCaller(prop, timeout);
75
+ return await caller(options);
76
+ };
77
+ return bridgeCaller(prop);
78
+ }
79
+ });
80
+ return proxyPage;
81
+ };
82
+ class AgentOverChromeBridge extends Agent {
83
+ async setDestroyOptionsAfterConnect() {
84
+ if (this.destroyAfterDisconnectFlag) this.page.setDestroyOptions({
85
+ closeTab: true
86
+ });
87
+ }
88
+ async connectNewTabWithUrl(url, options) {
89
+ await this.page.connectNewTabWithUrl(url, options);
90
+ await sleep(500);
91
+ await this.setDestroyOptionsAfterConnect();
92
+ }
93
+ async getBrowserTabList() {
94
+ return await this.page.getBrowserTabList();
95
+ }
96
+ async setActiveTabId(tabId) {
97
+ return await this.page.setActiveTabId(Number.parseInt(tabId));
98
+ }
99
+ async connectCurrentTab(options) {
100
+ await this.page.connectCurrentTab(options);
101
+ await sleep(500);
102
+ await this.setDestroyOptionsAfterConnect();
103
+ }
104
+ async aiAct(prompt, options) {
105
+ if (options) console.warn('the `options` parameter of aiAct is not supported in cli side');
106
+ return await super.aiAct(prompt);
107
+ }
108
+ async destroy(closeNewTabsAfterDisconnect) {
109
+ if ('boolean' == typeof closeNewTabsAfterDisconnect) await this.page.setDestroyOptions({
110
+ closeTab: closeNewTabsAfterDisconnect
111
+ });
112
+ await super.destroy();
113
+ }
114
+ constructor(opts){
115
+ const host = getBridgeServerHost({
116
+ host: opts?.host,
117
+ allowRemoteAccess: opts?.allowRemoteAccess
118
+ });
119
+ const page = getBridgePageInCliSide({
120
+ host,
121
+ port: opts?.port,
122
+ timeout: opts?.serverListeningTimeout,
123
+ closeConflictServer: opts?.closeConflictServer
124
+ });
125
+ const originalOnTaskStartTip = opts?.onTaskStartTip;
126
+ super(page, Object.assign(opts || {}, {
127
+ onTaskStartTip: (tip)=>{
128
+ this.page.showStatusMessage(tip);
129
+ if (originalOnTaskStartTip) originalOnTaskStartTip?.call(this, tip);
130
+ }
131
+ })), _define_property(this, "destroyAfterDisconnectFlag", void 0);
132
+ this.destroyAfterDisconnectFlag = opts?.closeNewTabsAfterDisconnect;
133
+ }
134
+ }
135
+ export { AgentOverChromeBridge, getBridgePageInCliSide };
@@ -0,0 +1,2 @@
1
+ import { ExtensionBridgePageBrowserSide } from "./page-browser-side.mjs";
2
+ export { ExtensionBridgePageBrowserSide };
@@ -0,0 +1,41 @@
1
+ const DefaultBridgeServerHost = '127.0.0.1';
2
+ const DefaultBridgeServerPort = 3766;
3
+ const DefaultLocalEndpoint = `http://${DefaultBridgeServerHost}:${DefaultBridgeServerPort}`;
4
+ const BridgeCallTimeout = 30000;
5
+ function getBridgeServerHost(options) {
6
+ if (options?.host) return options.host;
7
+ if (options?.allowRemoteAccess) return '0.0.0.0';
8
+ return DefaultBridgeServerHost;
9
+ }
10
+ var common_BridgeEvent = /*#__PURE__*/ function(BridgeEvent) {
11
+ BridgeEvent["Call"] = "bridge-call";
12
+ BridgeEvent["CallResponse"] = "bridge-call-response";
13
+ BridgeEvent["UpdateAgentStatus"] = "bridge-update-agent-status";
14
+ BridgeEvent["Message"] = "bridge-message";
15
+ BridgeEvent["Connected"] = "bridge-connected";
16
+ BridgeEvent["Refused"] = "bridge-refused";
17
+ BridgeEvent["ConnectNewTabWithUrl"] = "connectNewTabWithUrl";
18
+ BridgeEvent["ConnectCurrentTab"] = "connectCurrentTab";
19
+ BridgeEvent["GetBrowserTabList"] = "getBrowserTabList";
20
+ BridgeEvent["SetDestroyOptions"] = "setDestroyOptions";
21
+ BridgeEvent["SetActiveTabId"] = "setActiveTabId";
22
+ return BridgeEvent;
23
+ }({});
24
+ const BridgeSignalKill = 'MIDSCENE_BRIDGE_SIGNAL_KILL';
25
+ var common_MouseEvent = /*#__PURE__*/ function(MouseEvent) {
26
+ MouseEvent["PREFIX"] = "mouse.";
27
+ MouseEvent["Click"] = "mouse.click";
28
+ MouseEvent["Wheel"] = "mouse.wheel";
29
+ MouseEvent["Move"] = "mouse.move";
30
+ MouseEvent["Drag"] = "mouse.drag";
31
+ return MouseEvent;
32
+ }({});
33
+ var common_KeyboardEvent = /*#__PURE__*/ function(KeyboardEvent) {
34
+ KeyboardEvent["PREFIX"] = "keyboard.";
35
+ KeyboardEvent["Type"] = "keyboard.type";
36
+ KeyboardEvent["Press"] = "keyboard.press";
37
+ return KeyboardEvent;
38
+ }({});
39
+ const BridgePageType = 'page-over-chrome-extension-bridge';
40
+ const BridgeErrorCodeNoClientConnected = 'no-client-connected';
41
+ export { BridgeCallTimeout, BridgeErrorCodeNoClientConnected, common_BridgeEvent as BridgeEvent, BridgePageType, BridgeSignalKill, DefaultBridgeServerHost, DefaultBridgeServerPort, DefaultLocalEndpoint, common_KeyboardEvent as KeyboardEvent, common_MouseEvent as MouseEvent, getBridgeServerHost };
@@ -0,0 +1,4 @@
1
+ import { AgentOverChromeBridge } from "./agent-cli-side.mjs";
2
+ import { overrideAIConfig } from "@godscene/shared/env";
3
+ import { killRunningServer } from "./io-server.mjs";
4
+ export { AgentOverChromeBridge, killRunningServer, overrideAIConfig };
@@ -0,0 +1,99 @@
1
+ import { assert } from "@godscene/shared/utils";
2
+ import { io } from "socket.io-client";
3
+ import { BridgeEvent } from "./common.mjs";
4
+ function _define_property(obj, key, value) {
5
+ if (key in obj) Object.defineProperty(obj, key, {
6
+ value: value,
7
+ enumerable: true,
8
+ configurable: true,
9
+ writable: true
10
+ });
11
+ else obj[key] = value;
12
+ return obj;
13
+ }
14
+ class BridgeClient {
15
+ async connect() {
16
+ return new Promise((resolve, reject)=>{
17
+ const forceWebSocket = "u" < typeof XMLHttpRequest;
18
+ this.socket = io(this.endpoint, {
19
+ reconnection: false,
20
+ ...forceWebSocket ? {
21
+ transports: [
22
+ 'websocket'
23
+ ]
24
+ } : {},
25
+ query: {
26
+ version: "1.7.10"
27
+ }
28
+ });
29
+ const timeout = setTimeout(()=>{
30
+ try {
31
+ this.socket?.offAny();
32
+ this.socket?.close();
33
+ } catch (e) {
34
+ console.warn('got error when offing socket', e);
35
+ }
36
+ this.socket = null;
37
+ reject(new Error('failed to connect to bridge server after timeout'));
38
+ }, 5000);
39
+ this.socket.on('disconnect', (reason)=>{
40
+ this.socket = null;
41
+ this.onDisconnect?.();
42
+ });
43
+ this.socket.on('connect_error', (e)=>{
44
+ console.error('bridge-connect-error', e);
45
+ reject(new Error(e || 'bridge connect error'));
46
+ });
47
+ this.socket.on(BridgeEvent.Connected, (payload)=>{
48
+ clearTimeout(timeout);
49
+ this.serverVersion = payload?.version || 'unknown';
50
+ resolve(this.socket);
51
+ });
52
+ this.socket.on(BridgeEvent.Refused, (e)=>{
53
+ console.error('bridge-refused', e);
54
+ try {
55
+ this.socket?.disconnect();
56
+ } catch (e) {}
57
+ reject(new Error(e || 'bridge refused'));
58
+ });
59
+ this.socket.on(BridgeEvent.Call, (call)=>{
60
+ const id = call.id;
61
+ assert(void 0 !== id, 'call id is required');
62
+ (async ()=>{
63
+ let response;
64
+ try {
65
+ response = await this.onBridgeCall(call.method, call.args);
66
+ } catch (e) {
67
+ const errorContent = `Error from bridge client when calling, method: ${call.method}, args: ${call.args}, error: ${e?.message || e}\n${e?.stack || ''}`;
68
+ console.error(errorContent);
69
+ return this.socket?.emit(BridgeEvent.CallResponse, {
70
+ id,
71
+ error: errorContent
72
+ });
73
+ }
74
+ this.socket?.emit(BridgeEvent.CallResponse, {
75
+ id,
76
+ response
77
+ });
78
+ })();
79
+ });
80
+ });
81
+ }
82
+ disconnect() {
83
+ this.socket?.disconnect();
84
+ this.socket = null;
85
+ }
86
+ constructor(endpoint, onBridgeCall, onDisconnect){
87
+ _define_property(this, "endpoint", void 0);
88
+ _define_property(this, "onBridgeCall", void 0);
89
+ _define_property(this, "onDisconnect", void 0);
90
+ _define_property(this, "socket", void 0);
91
+ _define_property(this, "serverVersion", void 0);
92
+ this.endpoint = endpoint;
93
+ this.onBridgeCall = onBridgeCall;
94
+ this.onDisconnect = onDisconnect;
95
+ this.socket = null;
96
+ this.serverVersion = null;
97
+ }
98
+ }
99
+ export { BridgeClient };
@@ -0,0 +1,218 @@
1
+ import { createServer } from "node:http";
2
+ import { sleep } from "@godscene/core/utils";
3
+ import { logMsg } from "@godscene/shared/utils";
4
+ import { Server } from "socket.io";
5
+ import { io } from "socket.io-client";
6
+ import { BridgeCallTimeout, BridgeErrorCodeNoClientConnected, BridgeEvent, BridgeSignalKill, DefaultBridgeServerPort } from "./common.mjs";
7
+ function _define_property(obj, key, value) {
8
+ if (key in obj) Object.defineProperty(obj, key, {
9
+ value: value,
10
+ enumerable: true,
11
+ configurable: true,
12
+ writable: true
13
+ });
14
+ else obj[key] = value;
15
+ return obj;
16
+ }
17
+ const killRunningServer = async (port, host = 'localhost')=>{
18
+ try {
19
+ const client = io(`ws://${host}:${port || DefaultBridgeServerPort}`, {
20
+ query: {
21
+ [BridgeSignalKill]: 1
22
+ }
23
+ });
24
+ await sleep(300);
25
+ await client.close();
26
+ } catch (e) {}
27
+ };
28
+ class BridgeServer {
29
+ async listen(opts = {}) {
30
+ const { timeout = 30000 } = opts;
31
+ if (this.closeConflictServer) await killRunningServer(this.port, this.host);
32
+ return new Promise((resolve, reject)=>{
33
+ if (this.listeningTimerFlag) return reject(new Error('already listening'));
34
+ this.listeningTimerFlag = true;
35
+ this.listeningTimeoutId = timeout ? setTimeout(()=>{
36
+ reject(new Error(`no extension connected after ${timeout}ms (${BridgeErrorCodeNoClientConnected})`));
37
+ }, timeout) : null;
38
+ this.connectionTipTimer = !timeout || timeout > 3000 ? setTimeout(()=>{
39
+ logMsg('waiting for bridge to connect...');
40
+ }, 2000) : null;
41
+ const httpServer = createServer();
42
+ httpServer.once('listening', ()=>{
43
+ resolve();
44
+ });
45
+ httpServer.once('error', (err)=>{
46
+ reject(new Error(`Bridge Listening Error: ${err.message}`));
47
+ });
48
+ if ('127.0.0.1' === this.host) httpServer.listen(this.port);
49
+ else httpServer.listen(this.port, this.host);
50
+ this.io = new Server(httpServer, {
51
+ maxHttpBufferSize: 104857600,
52
+ pingTimeout: 60000
53
+ });
54
+ this.io.use((socket, next)=>{
55
+ if (socket.handshake.url.includes(BridgeSignalKill)) return next();
56
+ if (this.socket?.connected) return next(new Error('server already connected by another client'));
57
+ next();
58
+ });
59
+ this.io.on('connection', (socket)=>{
60
+ const url = socket.handshake.url;
61
+ if (url.includes(BridgeSignalKill)) {
62
+ console.warn('kill signal received, closing bridge server');
63
+ return this.close();
64
+ }
65
+ this.connectionLost = false;
66
+ this.connectionLostReason = '';
67
+ this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);
68
+ this.listeningTimeoutId = null;
69
+ this.connectionTipTimer && clearTimeout(this.connectionTipTimer);
70
+ this.connectionTipTimer = null;
71
+ if (this.socket?.connected) {
72
+ socket.emit(BridgeEvent.Refused);
73
+ socket.disconnect();
74
+ logMsg('refused new connection: server already connected by another client');
75
+ return;
76
+ }
77
+ if (this.socket) {
78
+ try {
79
+ this.socket.disconnect();
80
+ } catch (e) {
81
+ logMsg(`failed to disconnect stale socket: ${e}`);
82
+ }
83
+ this.socket = null;
84
+ }
85
+ try {
86
+ logMsg('one client connected');
87
+ this.socket = socket;
88
+ const clientVersion = socket.handshake.query.version;
89
+ logMsg(`Bridge connected, cli-side version v1.7.10, browser-side version v${clientVersion}`);
90
+ socket.on(BridgeEvent.CallResponse, (params)=>{
91
+ const id = params.id;
92
+ const response = params.response;
93
+ const error = params.error;
94
+ this.triggerCallResponseCallback(id, error, response);
95
+ });
96
+ socket.on('disconnect', (reason)=>{
97
+ this.connectionLost = true;
98
+ this.connectionLostReason = reason;
99
+ this.socket = null;
100
+ for(const id in this.calls){
101
+ const call = this.calls[id];
102
+ if (!call.responseTime) {
103
+ const errorMessage = this.connectionLostErrorMsg();
104
+ this.triggerCallResponseCallback(id, new Error(errorMessage), null);
105
+ }
106
+ }
107
+ for(const id in this.calls)if (this.calls[id].responseTime) delete this.calls[id];
108
+ this.onDisconnect?.(reason);
109
+ });
110
+ setTimeout(()=>{
111
+ this.onConnect?.();
112
+ const payload = {
113
+ version: "1.7.10"
114
+ };
115
+ socket.emit(BridgeEvent.Connected, payload);
116
+ Promise.resolve().then(()=>{
117
+ for(const id in this.calls)if (0 === this.calls[id].callTime) this.emitCall(id);
118
+ });
119
+ }, 0);
120
+ } catch (e) {
121
+ logMsg(`failed to handle connection event: ${e}`);
122
+ }
123
+ });
124
+ this.io.on('close', ()=>{
125
+ this.close();
126
+ });
127
+ });
128
+ }
129
+ async triggerCallResponseCallback(id, error, response) {
130
+ const call = this.calls[id];
131
+ if (!call) throw new Error(`call ${id} not found`);
132
+ if (error) call.error = error instanceof Error ? error : new Error('string' == typeof error ? error : String(error));
133
+ else call.error = void 0;
134
+ call.response = response;
135
+ call.responseTime = Date.now();
136
+ call.callback(call.error, response);
137
+ }
138
+ async emitCall(id) {
139
+ const call = this.calls[id];
140
+ if (!call) throw new Error(`call ${id} not found`);
141
+ if (this.connectionLost) {
142
+ const message = `Connection lost, reason: ${this.connectionLostReason}`;
143
+ call.callback(new Error(message), null);
144
+ return;
145
+ }
146
+ if (this.socket) {
147
+ this.socket.emit(BridgeEvent.Call, {
148
+ id,
149
+ method: call.method,
150
+ args: call.args
151
+ });
152
+ call.callTime = Date.now();
153
+ }
154
+ }
155
+ async call(method, args, timeout = BridgeCallTimeout) {
156
+ const id = `${this.callId++}`;
157
+ return new Promise((resolve, reject)=>{
158
+ const timeoutId = setTimeout(()=>{
159
+ logMsg(`bridge call timeout, id=${id}, method=${method}, args=`, args);
160
+ this.calls[id].error = new Error(`Bridge call timeout after ${timeout}ms: ${method}`);
161
+ reject(this.calls[id].error);
162
+ }, timeout);
163
+ this.calls[id] = {
164
+ method,
165
+ args,
166
+ response: null,
167
+ callTime: 0,
168
+ responseTime: 0,
169
+ callback: (error, response)=>{
170
+ clearTimeout(timeoutId);
171
+ if (error) reject(error);
172
+ else resolve(response);
173
+ }
174
+ };
175
+ this.emitCall(id);
176
+ });
177
+ }
178
+ async close() {
179
+ this.listeningTimeoutId && clearTimeout(this.listeningTimeoutId);
180
+ this.connectionTipTimer && clearTimeout(this.connectionTipTimer);
181
+ const closeProcess = this.io?.close();
182
+ this.io = null;
183
+ return closeProcess;
184
+ }
185
+ constructor(host, port, onConnect, onDisconnect, closeConflictServer){
186
+ _define_property(this, "host", void 0);
187
+ _define_property(this, "port", void 0);
188
+ _define_property(this, "onConnect", void 0);
189
+ _define_property(this, "onDisconnect", void 0);
190
+ _define_property(this, "closeConflictServer", void 0);
191
+ _define_property(this, "callId", void 0);
192
+ _define_property(this, "io", void 0);
193
+ _define_property(this, "socket", void 0);
194
+ _define_property(this, "listeningTimeoutId", void 0);
195
+ _define_property(this, "listeningTimerFlag", void 0);
196
+ _define_property(this, "connectionTipTimer", void 0);
197
+ _define_property(this, "calls", void 0);
198
+ _define_property(this, "connectionLost", void 0);
199
+ _define_property(this, "connectionLostReason", void 0);
200
+ _define_property(this, "connectionLostErrorMsg", void 0);
201
+ this.host = host;
202
+ this.port = port;
203
+ this.onConnect = onConnect;
204
+ this.onDisconnect = onDisconnect;
205
+ this.closeConflictServer = closeConflictServer;
206
+ this.callId = 0;
207
+ this.io = null;
208
+ this.socket = null;
209
+ this.listeningTimeoutId = null;
210
+ this.listeningTimerFlag = false;
211
+ this.connectionTipTimer = null;
212
+ this.calls = {};
213
+ this.connectionLost = false;
214
+ this.connectionLostReason = '';
215
+ this.connectionLostErrorMsg = ()=>`Connection lost, reason: ${this.connectionLostReason}`;
216
+ }
217
+ }
218
+ export { BridgeServer, killRunningServer };
@@ -0,0 +1,119 @@
1
+ import { assert } from "@godscene/shared/utils";
2
+ import page from "../chrome-extension/page.mjs";
3
+ import { BridgeEvent, DefaultBridgeServerPort, KeyboardEvent, MouseEvent } from "./common.mjs";
4
+ import { BridgeClient } from "./io-client.mjs";
5
+ function _define_property(obj, key, value) {
6
+ if (key in obj) Object.defineProperty(obj, key, {
7
+ value: value,
8
+ enumerable: true,
9
+ configurable: true,
10
+ writable: true
11
+ });
12
+ else obj[key] = value;
13
+ return obj;
14
+ }
15
+ class ExtensionBridgePageBrowserSide extends page {
16
+ async setupBridgeClient() {
17
+ const endpoint = this.serverEndpoint || `ws://localhost:${DefaultBridgeServerPort}`;
18
+ let resolveConfirmationGate = ()=>{};
19
+ if (this.onConnectionRequest) this.confirmationPromise = new Promise((resolve)=>{
20
+ resolveConfirmationGate = resolve;
21
+ });
22
+ this.bridgeClient = new BridgeClient(endpoint, async (method, args)=>{
23
+ if (this.confirmationPromise) {
24
+ const allowed = await this.confirmationPromise;
25
+ if (!allowed) throw new Error('Connection denied by user');
26
+ }
27
+ this.onLogMessage(`bridge call from cli side: ${method}`, 'log');
28
+ if (method === BridgeEvent.ConnectNewTabWithUrl) return this.connectNewTabWithUrl.apply(this, args);
29
+ if (method === BridgeEvent.GetBrowserTabList) return this.getBrowserTabList.apply(this, args);
30
+ if (method === BridgeEvent.SetActiveTabId) return this.setActiveTabId.apply(this, args);
31
+ if (method === BridgeEvent.ConnectCurrentTab) return this.connectCurrentTab.apply(this, args);
32
+ if (method === BridgeEvent.UpdateAgentStatus) return this.onLogMessage(args[0], 'status');
33
+ const tabId = await this.getActiveTabId();
34
+ if (!tabId || 0 === tabId) throw new Error('no tab is connected');
35
+ if (method.startsWith(MouseEvent.PREFIX)) {
36
+ const actionName = method.split('.')[1];
37
+ return this.mouse[actionName].apply(this.mouse, args);
38
+ }
39
+ if (method.startsWith(KeyboardEvent.PREFIX)) {
40
+ const actionName = method.split('.')[1];
41
+ return this.keyboard[actionName].apply(this.keyboard, args);
42
+ }
43
+ if (!this[method]) return void this.onLogMessage(`method not found: ${method}`, 'log');
44
+ try {
45
+ const result = await this[method](...args);
46
+ return result;
47
+ } catch (e) {
48
+ const errorMessage = e instanceof Error ? e.message : 'Unknown error';
49
+ this.onLogMessage(`Error calling method: ${method}, ${errorMessage}`, 'log');
50
+ throw new Error(errorMessage, {
51
+ cause: e
52
+ });
53
+ }
54
+ }, ()=>this.destroy());
55
+ await this.bridgeClient.connect();
56
+ if (this.onConnectionRequest) {
57
+ this.onLogMessage('Waiting for user confirmation...', 'log');
58
+ const allowed = await this.onConnectionRequest();
59
+ resolveConfirmationGate(allowed);
60
+ this.confirmationPromise = null;
61
+ if (!allowed) {
62
+ this.onLogMessage('Connection denied by user', 'log');
63
+ this.bridgeClient.disconnect();
64
+ this.bridgeClient = null;
65
+ throw new Error('Connection denied by user');
66
+ }
67
+ }
68
+ this.onLogMessage(`Bridge connected, cli-side version v${this.bridgeClient.serverVersion}, browser-side version v1.7.10`, 'log');
69
+ }
70
+ async connect() {
71
+ return await this.setupBridgeClient();
72
+ }
73
+ async connectNewTabWithUrl(url, options = {
74
+ forceSameTabNavigation: true
75
+ }) {
76
+ const tab = await chrome.tabs.create({
77
+ url
78
+ });
79
+ const tabId = tab.id;
80
+ assert(tabId, 'failed to get tabId after creating a new tab');
81
+ this.onLogMessage(`Creating new tab: ${url}`, 'log');
82
+ this.newlyCreatedTabIds.push(tabId);
83
+ if (options?.forceSameTabNavigation) this.forceSameTabNavigation = true;
84
+ await this.setActiveTabId(tabId);
85
+ }
86
+ async connectCurrentTab(options = {
87
+ forceSameTabNavigation: true
88
+ }) {
89
+ const tabs = await chrome.tabs.query({
90
+ active: true,
91
+ currentWindow: true
92
+ });
93
+ const tabId = tabs[0]?.id;
94
+ assert(tabId, 'failed to get tabId');
95
+ this.onLogMessage(`Connected to current tab: ${tabs[0]?.url}`, 'log');
96
+ if (options?.forceSameTabNavigation) this.forceSameTabNavigation = true;
97
+ await this.setActiveTabId(tabId);
98
+ }
99
+ async setDestroyOptions(options) {
100
+ this.destroyOptions = options;
101
+ }
102
+ async destroy() {
103
+ if (this.destroyOptions?.closeTab && this.newlyCreatedTabIds.length > 0) {
104
+ this.onLogMessage('Closing all newly created tabs by bridge...', 'log');
105
+ for (const tabId of this.newlyCreatedTabIds)await chrome.tabs.remove(tabId);
106
+ this.newlyCreatedTabIds = [];
107
+ }
108
+ await super.destroy();
109
+ if (this.bridgeClient) {
110
+ this.bridgeClient.disconnect();
111
+ this.bridgeClient = null;
112
+ this.onDisconnect();
113
+ }
114
+ }
115
+ constructor(serverEndpoint, onDisconnect = ()=>{}, onLogMessage = ()=>{}, forceSameTabNavigation = true, onConnectionRequest){
116
+ super(forceSameTabNavigation), _define_property(this, "serverEndpoint", void 0), _define_property(this, "onDisconnect", void 0), _define_property(this, "onLogMessage", void 0), _define_property(this, "onConnectionRequest", void 0), _define_property(this, "bridgeClient", void 0), _define_property(this, "destroyOptions", void 0), _define_property(this, "newlyCreatedTabIds", void 0), _define_property(this, "confirmationPromise", void 0), this.serverEndpoint = serverEndpoint, this.onDisconnect = onDisconnect, this.onLogMessage = onLogMessage, this.onConnectionRequest = onConnectionRequest, this.bridgeClient = null, this.newlyCreatedTabIds = [], this.confirmationPromise = null;
117
+ }
118
+ }
119
+ export { ExtensionBridgePageBrowserSide };
@@ -0,0 +1,7 @@
1
+ import { tmpdir } from "node:os";
2
+ import { join } from "node:path";
3
+ const PROXY_ENDPOINT_FILE = join(tmpdir(), 'midscene-cdp-proxy-endpoint');
4
+ const PROXY_PID_FILE = join(tmpdir(), 'midscene-cdp-proxy-pid');
5
+ const PROXY_UPSTREAM_FILE = join(tmpdir(), 'midscene-cdp-proxy-upstream');
6
+ const TARGET_ID_FILE = join(tmpdir(), 'midscene-cdp-target-id');
7
+ export { PROXY_ENDPOINT_FILE, PROXY_PID_FILE, PROXY_UPSTREAM_FILE, TARGET_ID_FILE };