UNPKG

@grasplabs/grasp

Version:

TypeScript SDK for browser automation and secure command execution in highly available and scalable cloud browser environments

133 lines 6.19 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CodeRunner = void 0; const logger_1 = require("../utils/logger"); const detect_code_1 = require("../utils/detect-code"); const uuid_1 = require("uuid"); class CodeRunner { /** * Gets or creates a default logger instance * @returns Logger instance */ getDefaultLogger() { try { return (0, logger_1.getLogger)().child('RunnerService'); } catch (error) { // If logger is not initialized, create a default one const defaultLogger = new logger_1.Logger({ level: 'info', console: true, }); return defaultLogger.child('RunnerService'); } } constructor(session) { this.logger = this.getDefaultLogger(); this.session = session; this.logger.info('CodeRunner initialized'); } injectCode(code, type) { const wsUrl = this.session.browser.getEndpoint(); if (type === 'py') { // 替换 python 调用 code = code.replace(/\.launch\s*\(/gm, `.connect_over_cdp('${wsUrl}',`); code = code.replace(/\.launch_persistent_context\s*\(/gm, `.connect_over_cdp('${wsUrl}',`); // 移除 launch_persistent_context 调用中的 user_data_dir 参数(位置参数) code = code.replace(/\.connect_over_cdp\('([^']+)',\s*'[^']*',/gm, `.connect_over_cdp('$1',`); code = code.replace(/\.connect_over_cdp\('([^']+)',\s*"[^"]*",/gm, `.connect_over_cdp('$1',`); // 移除单独的位置参数(没有其他参数的情况) code = code.replace(/\.connect_over_cdp\('([^']+)',\s*'[^']*'\s*\)/gm, `.connect_over_cdp('$1')`); code = code.replace(/\.connect_over_cdp\('([^']+)',\s*"[^"]*"\s*\)/gm, `.connect_over_cdp('$1')`); // 只保留 slow_mo 和 timeout 参数,其他所有具名参数都删除 const paramsToReserve = ['slow_mo', 'timeout']; // 移除所有不在保留列表中的具名参数 // 使用通用正则表达式匹配所有具名参数,然后只保留指定的参数 code = code.replace(/\.connect_over_cdp\('([^']+)'([^)]*?)\)/gm, (match, url, params) => { if (!params.trim()) { return `.connect_over_cdp('${url}')`; } // 提取所有参数 const reservedParams = []; paramsToReserve.forEach(param => { // 匹配简单值参数 const simpleMatch = params.match(new RegExp(`\\b${param}\\s*=\\s*[^,)\\n{\\[]+`, 'g')); if (simpleMatch) { reservedParams.push(...simpleMatch); } // 匹配字典参数 const dictMatch = params.match(new RegExp(`\\b${param}\\s*=\\s*\\{[^}]*\\}`, 'g')); if (dictMatch) { reservedParams.push(...dictMatch); } // 匹配列表参数 const listMatch = params.match(new RegExp(`\\b${param}\\s*=\\s*\\[[^\\]]*\\]`, 'g')); if (listMatch) { reservedParams.push(...listMatch); } }); // 构建结果字符串 let result; if (reservedParams.length === 0) { result = `.connect_over_cdp('${url}')`; } else { result = `.connect_over_cdp('${url}', ${reservedParams.join(', ')})`; } // 只对当前匹配的函数调用进行清理,避免影响其他代码 result = result.replace(/,\s*\)/g, ')'); result = result.replace(/\(\s*,/g, '('); result = result.replace(/,\s*,/g, ','); // 处理只有路径参数的情况,移除多余的逗号 result = result.replace(/\.connect_over_cdp\('([^']+)',\s*\)/g, `.connect_over_cdp('$1')`); return result; }); } else { // 替换 js 调用 code = code.replace(/\.launch\s*\(/gm, `.connectOverCDP('${wsUrl}',`); code = code.replace(/\.launchPersistentContext\s*\([^,]*?,/gm, `.connectOverCDP('${wsUrl}',`); } return code; } async run(code, options = {}) { const type = (0, detect_code_1.detectCodeType)(code); if (options.inject) { code = this.injectCode(code, type); } const scriptFile = `/home/user/codes/${(0, uuid_1.v4)()}-script.${type === 'esm' ? 'mjs' : type}`; await this.session.files.writeFile(scriptFile, code); const terminal = this.session.terminal; const timeout = options.timeout || 300000; const result = await terminal.runCommand(`${type === 'py' ? 'python3' : 'node'} ${scriptFile}`); result.stderr.pipe(process.stderr); result.stdout.pipe(process.stdout); const timer = setTimeout(() => { this.logger.error('🕖 code execution timeout'); result.kill(); }, timeout); return result .json() .then(async (result) => { clearTimeout(timer); let localPath = null; try { this.logger.info('Script execution completed, syncing downloads directory'); localPath = await this.session.files.syncDownloadsDirectory(options.syncDir || './code-runner', './code-runner'); this.logger.info('Downloads directory synced successfully'); } catch (error) { this.logger.error('Failed to sync downloads directory:', error); } finally { // eslint-disable-next-line no-unsafe-finally return { ...result, outputDir: localPath, }; } }); } } exports.CodeRunner = CodeRunner; //# sourceMappingURL=code-runner.js.map