diff --git a/README.md b/README.md index b6b8170..0f2d430 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ pnpm run build pnpm run test # Lint code -pnpm run lint +pnpm run format ```
diff --git a/README.zh-CN.md b/README.zh-CN.md index dc8a7d3..616111a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -63,7 +63,7 @@ pnpm run build pnpm run test # 代码检查 -pnpm run lint +pnpm run format ```
diff --git a/packages/browser-ui/README.md b/packages/browser-ui/README.md index 6f68308..1bdfb1a 100644 --- a/packages/browser-ui/README.md +++ b/packages/browser-ui/README.md @@ -40,6 +40,33 @@ BrowserUI.create({ }); ``` +Or use the unpkg CDN to use it on any webpage: + +- **CDN URL**: https://unpkg.com/@agent-infra/browser/dist/bundle/index.js + +```html + + + +
+ + + + +``` + A complete usable example, which can be run directly with `npm run dev` in the current directory or viewed in the `/examples` directory within the package. ## Features diff --git a/packages/browser-ui/README.zh-CN.md b/packages/browser-ui/README.zh-CN.md index ba8131c..9eb4e0b 100644 --- a/packages/browser-ui/README.zh-CN.md +++ b/packages/browser-ui/README.zh-CN.md @@ -40,8 +40,37 @@ BrowserUI.create({ }); ``` +或者直接使用 unpkg CDN 在任意网页中使用: + +- **CDN URL**: https://unpkg.com/@agent-infra/browser/dist/bundle/index.js + +```html + + + +
+ + + + +``` + 完整的可用示例,可直接在当前目录下运行 `npm run dev` 或查看包中的 `/examples` 目录。 +
+ ## 核心功能 关于所有功能的详细文档和 API 参考,请访问我们的[完整文档](https://github.com/agent-infra/browser/blob/main/docs/browser-ui.zh-CN.md)。 diff --git a/packages/browser-ui/examples/bundle/boot.ts b/packages/browser-ui/examples/bundle/boot.ts new file mode 100644 index 0000000..f798241 --- /dev/null +++ b/packages/browser-ui/examples/bundle/boot.ts @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2025 Bytedance, Inc. and its affiliates. + * SPDX-License-Identifier: Apache-2.0 + */ +import puppeteer from 'puppeteer-core'; +import { createServer } from 'http'; +import { readFile, stat } from 'fs/promises'; +import { URL, fileURLToPath } from 'url'; +import { join, dirname } from 'path'; + +async function main() { + console.log('🚀 launch ...'); + + const browser = await puppeteer.launch({ + executablePath: + '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', + headless: false, + defaultViewport: { + width: 900, + height: 900, + deviceScaleFactor: 0, + }, + args: [ + '--mute-audio', + '--no-default-browser-check', + '--window-size=900,990', + '--remote-allow-origins=http://127.0.0.1:3000', + 'https://www.bytedance.com/en/', + ], + ignoreDefaultArgs: ['--enable-automation'], + }); + + const wsEndpoint = browser.wsEndpoint(); + + console.log('✅ launched successfully!'); + console.log('📡 CDP WebSocket:', wsEndpoint); + + const port = 3000; + const server = createServer(async (req, res) => { + try { + console.log(`Request: ${req.method} ${req.url}`); + + if (req.url === '/' || req.url === '/index.html') { + const indexPath = new URL('./index.html', import.meta.url).pathname; + console.log(`Reading file: ${indexPath}`); + + let html = await readFile(indexPath, 'utf-8'); + console.log('File read successfully'); + + // Replace the import.meta.WSEndpoint with the actual wsEndpoint + html = html.replace( + 'import.meta.WSEndpoint', + JSON.stringify(wsEndpoint), + ); + + console.log('HTML length:', html.length); + + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(html); + } else if (req.url?.startsWith('/dist/')) { + // Handle static files from /dist/ directory + const __filename = fileURLToPath(import.meta.url); + const __dirname = dirname(__filename); + const staticPath = join( + __dirname, + '..', + '..', + 'dist', + req.url.replace('/dist/', ''), + ); + + console.log(`Static file request: ${staticPath}`); + + try { + const fileStat = await stat(staticPath); + if (fileStat.isFile()) { + const content = await readFile(staticPath); + res.writeHead(200, { 'Content-Type': 'application/javascript' }); + res.end(content); + return; + } + } catch (staticError) { + console.error('Static file not found:', staticError); + } + + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('Static file not found'); + } else { + console.log('404 for:', req.url); + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('Not Found'); + } + } catch (error) { + console.error('Server error:', error); + res.writeHead(500, { 'Content-Type': 'text/plain' }); + res.end(`Internal Server Error: ${(error as Error).message}`); + } + }); + + server.listen(port, () => { + console.log(`🌐 Server running at http://localhost:${port}`); + console.log('🔄 Ctrl+C to exit...\n'); + }); + + const cleanup = async () => { + try { + console.log('\n🛑 closing server and browser...'); + if (server) { + server.close(); + } + if (browser) { + await browser.close(); + } + } catch (e) { + // ignore + } finally { + process.exit(0); + } + }; + + process.on('SIGINT', cleanup); + process.on('SIGTERM', cleanup); +} + +main().catch(console.error); diff --git a/packages/browser-ui/examples/bundle/index.html b/packages/browser-ui/examples/bundle/index.html new file mode 100644 index 0000000..2639fee --- /dev/null +++ b/packages/browser-ui/examples/bundle/index.html @@ -0,0 +1,47 @@ + + + + + + CanvasBrowser Demo + + + + +
+ +
+ + + + + diff --git a/packages/browser-ui/examples/boot.ts b/packages/browser-ui/examples/core/boot.ts similarity index 100% rename from packages/browser-ui/examples/boot.ts rename to packages/browser-ui/examples/core/boot.ts diff --git a/packages/browser-ui/package.json b/packages/browser-ui/package.json index 455e447..e296e28 100644 --- a/packages/browser-ui/package.json +++ b/packages/browser-ui/package.json @@ -29,8 +29,8 @@ ], "scripts": { "prepublishOnly": "pnpm run build", - "build": "rslib build", - "dev": "tsx examples/boot.ts", + "build": "rslib build && rslib build --config rslib.config.bundle.ts", + "dev": "tsx examples/core/boot.ts", "test": "vitest run" }, "dependencies": { diff --git a/packages/browser-ui/rslib.config.bundle.ts b/packages/browser-ui/rslib.config.bundle.ts new file mode 100644 index 0000000..7652857 --- /dev/null +++ b/packages/browser-ui/rslib.config.bundle.ts @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2025 Bytedance, Inc. and its affiliates. + * SPDX-License-Identifier: Apache-2.0 + */ +// import { pluginReact } from '@rsbuild/plugin-react'; +import { defineConfig } from '@rslib/core'; + +const BANNER = `/** +* Copyright (c) 2025 Bytedance, Inc. and its affiliates. +* SPDX-License-Identifier: Apache-2.0 +*/`; + +export default defineConfig({ + source: { + entry: { + index: 'src/index.ts', + }, + decorators: { + version: 'legacy', + }, + }, + lib: [ + { + format: 'umd', + syntax: 'es2021', + bundle: true, + dts: false, + banner: { js: BANNER }, + umdName: 'agent_infra_browser_ui', + output: { + minify: true, + externals: ['chromium-bidi/lib/cjs/bidiMapper/BidiMapper.js'], + distPath: { + root: 'dist/bundle/', + }, + }, + }, + ], + // performance: { + // bundleAnalyze: {}, + // }, + output: { + target: 'web', + sourceMap: true, + }, +}); diff --git a/packages/browser-ui/rslib.config.ts b/packages/browser-ui/rslib.config.ts index f3848d0..01aaba1 100644 --- a/packages/browser-ui/rslib.config.ts +++ b/packages/browser-ui/rslib.config.ts @@ -39,6 +39,5 @@ export default defineConfig({ target: 'web', cleanDistPath: true, sourceMap: true, - externals: ['chromium-bidi/lib/cjs/bidiMapper/BidiMapper.js'], }, }); diff --git a/packages/browser/src/actions/keyboard.ts b/packages/browser/src/actions/keyboard.ts index 1070d17..a07d2d1 100644 --- a/packages/browser/src/actions/keyboard.ts +++ b/packages/browser/src/actions/keyboard.ts @@ -46,7 +46,11 @@ export class Keyboard { const formattedHotkey = this.#formatHotkey(key); if (this.#env.osName === 'macOS' && this.#env.browserName === 'Chrome') { - const success = await this.#macOSCDPHotKey(formattedHotkey, options); + const success = await this.#macOSCDPHotKey( + formattedHotkey, + options, + true, + ); if (success) { return { success: true }; } @@ -78,7 +82,11 @@ export class Keyboard { const formattedHotkey = this.#formatHotkey(key); if (this.#env.osName === 'macOS' && this.#env.browserName === 'Chrome') { - const success = await this.#macOSCDPHotKey(formattedHotkey, options); + const success = await this.#macOSCDPHotKey( + formattedHotkey, + options, + false, + ); if (success) { return { success: true }; } @@ -163,6 +171,7 @@ export class Keyboard { async #macOSCDPHotKey( keys: KeyInput[], options: Readonly, + isPress: boolean, ): Promise { const hotkey = keys .map((key) => { @@ -180,7 +189,10 @@ export class Keyboard { commands: [command.commands], }); await delay(options.delay ?? 0); - await this.#page.keyboard.up(command.key); + + if (isPress) { + await this.#page.keyboard.up(command.key); + } return true; }