diff --git a/dev_test_scripts/debug/debug_cdp_network.js b/dev_test_scripts/debug/debug_cdp_network.js new file mode 100644 index 0000000..7b1015b --- /dev/null +++ b/dev_test_scripts/debug/debug_cdp_network.js @@ -0,0 +1,34 @@ +// CDP 运行时取证:监听 console + network,手动广播 goToOssAddress,观察列表请求 +const sleep = ms => new Promise(r => setTimeout(r, ms)) +;(async () => { + const list = await (await fetch('http://127.0.0.1:9222/json')).json() + const page = (list || []).find(t => t.type === 'page' && /index\.html/.test(t.url || '')) + if (!page) { console.log('no page'); process.exit(1) } + const ws = new WebSocket(page.webSocketDebuggerUrl) + await new Promise((res, rej) => { ws.onopen = res; ws.onerror = rej }) + let mid = 0 + const send = (method, params) => ws.send(JSON.stringify({ id: ++mid, method, params: params || {} })) + ws.onmessage = ev => { + const m = JSON.parse(ev.data) + if (m.method === 'Runtime.consoleAPICalled') { + const args = (m.params.args || []).map(a => a.value || a.description || '').join(' ') + if (/ossAddressChange|goToOssAddress|listFiles|AccessDenied|Error/i.test(args)) console.log('[console]', args.slice(0, 300)) + } + if (m.method === 'Network.requestWillBeSent') { + const u = m.params.request.url + if (/aliyuncs/.test(u)) console.log('[req]', m.params.request.method, u.slice(0, 160)) + } + if (m.method === 'Network.responseReceived') { + const u = m.params.response.url + if (/aliyuncs/.test(u)) console.log('[rsp]', m.params.response.status, u.slice(0, 160)) + } + } + send('Runtime.enable') + send('Network.enable') + await sleep(2000) + console.log('--- 手动广播 goToOssAddress ---') + send('Runtime.evaluate', { expression: `angular.element(document.querySelector('[ng-app]')).injector().get('$rootScope').$broadcast('goToOssAddress','oss://android-o1-images/trae-files-temp/'); 'ok'` }) + await sleep(8000) + ws.close() + process.exit(0) +})() diff --git a/dev_test_scripts/debug/debug_cdp_probe_only.js b/dev_test_scripts/debug/debug_cdp_probe_only.js new file mode 100644 index 0000000..3b02a3e --- /dev/null +++ b/dev_test_scripts/debug/debug_cdp_probe_only.js @@ -0,0 +1,27 @@ +// 仅连接 CDP 读取 __trae_dbg(配合手动启动的带调试端口实例) +const sleep = ms => new Promise(r => setTimeout(r, ms)) +;(async () => { + let page = null + for (let i = 0; i < 20 && !page; i++) { + await sleep(1000) + try { + const list = await (await fetch('http://127.0.0.1:9222/json')).json() + page = (list || []).find(t => t.type === 'page' && /index\.html/.test(t.url || '')) + } catch {} + } + if (!page) { console.log('no page target'); process.exit(1) } + const ws = new WebSocket(page.webSocketDebuggerUrl) + await new Promise((res, rej) => { ws.onopen = res; ws.onerror = rej }) + for (let i = 0; i < 10; i++) { + await sleep(2000) + const r = await new Promise(resolve => { + const id = 777 + i + const onMsg = ev => { const m = JSON.parse(ev.data); if (m.id === id) { ws.removeEventListener('message', onMsg); resolve(m.result) } } + ws.addEventListener('message', onMsg) + ws.send(JSON.stringify({ id, method: 'Runtime.evaluate', params: { expression: 'JSON.stringify({dbg: window.__trae_dbg || null, hasNg: !!window.angular, url: location.href})', returnByValue: true } })) + }) + console.log(`t+${(i + 1) * 2}s`, r.result && r.result.value) + } + ws.close() + process.exit(0) +})() diff --git a/dev_test_scripts/debug/debug_injection_syntax.js b/dev_test_scripts/debug/debug_injection_syntax.js new file mode 100644 index 0000000..156ed4e --- /dev/null +++ b/dev_test_scripts/debug/debug_injection_syntax.js @@ -0,0 +1,24 @@ +// 提取诊断版 main.js 中的 injection 字符串并做语法检查 + 直接通过 CDP 手动执行验证 +const fs = require('fs') +const src = fs.readFileSync('temp\\ossbrowser_inspect\\oss-browser-win32-x64\\resources\\app\\main.js', 'utf8') +const start = src.indexOf("const injection =") +const end = src.indexOf("app.on('web-contents-created'") +const chunk = src.slice(start, end) +// 在 Node 里执行这段拼接代码,得到 injection 字符串 +const fn = new Function('CFG', chunk + '; return injection;') +let injection +try { + injection = fn({ target: 'oss://android-o1-images/trae-files-temp/', region: 'oss-cn-shanghai' }) +} catch (e) { + console.log('[FAIL] 拼接代码执行失败:', e.message) + process.exit(1) +} +console.log('injection length:', injection.length) +try { + new Function(injection) + console.log('[OK] injection 语法正确') +} catch (e) { + console.log('[FAIL] injection 语法错误:', e.message) + console.log(injection.slice(0, 500)) +} +fs.writeFileSync('temp\\injection_extract.js', injection) diff --git a/dev_test_scripts/debug/debug_oss_list_permission.js b/dev_test_scripts/debug/debug_oss_list_permission.js new file mode 100644 index 0000000..75be8ad --- /dev/null +++ b/dev_test_scripts/debug/debug_oss_list_permission.js @@ -0,0 +1,82 @@ +/** + * 诊断 DOC_CLOUD_KEEPER 这对 Key 在 android-o1-images(cn-beijing)的权限边界: + * 1) ListBuckets(账号级) + * 2) ListObjectsV2 prefix=trae-files-temp/(bucket 级) + * 3) GetBucketInfo + * 只读探测,不做任何写/删操作。 + * 用法:node dev_test_scripts/debug/debug_oss_list_permission.js + */ +const fs = require('fs') +const os = require('os') +const path = require('path') +const crypto = require('crypto') +const https = require('https') + +const BUCKET = 'android-o1-images' +const ENDPOINT = 'oss-cn-beijing.aliyuncs.com' + +const loadEnv = () => { + const out = {} + try { + const text = fs.readFileSync(path.join(os.homedir(), 'Toolbox_local_creds.env.local'), 'utf8') + for (const line of text.split(/\r?\n/)) { + const t = line.trim() + if (!t || t.startsWith('#')) continue + const eq = t.indexOf('=') + if (eq <= 0) continue + out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim() + } + } catch {} + return out +} + +const ENV = loadEnv() +const AK = ENV.DOC_CLOUD_KEEPER_OSS_ACCESS_KEY_ID || '' +const SK = ENV.DOC_CLOUD_KEEPER_OSS_ACCESS_KEY_SECRET || '' +if (!AK || !SK) { console.error('[FAIL] 缺少 DOC_CLOUD_KEEPER key'); process.exit(1) } + +// 只打印 env 里所有 OSS 相关 key 的“名字”(不打印值),看有没有其他可用凭据 +console.log('env 中 OSS 相关 key 名:', Object.keys(ENV).filter(k => /OSS|ALIYUN/i.test(k)).join(', ')) + +const sign = (method, date, resource, contentType = '') => { + const stringToSign = `${method}\n\n${contentType}\n${date}\n${resource}` + return crypto.createHmac('sha1', SK).update(stringToSign).digest('base64') +} + +const ossRequest = (method, hostname, urlPath, resource) => new Promise((resolve, reject) => { + const date = new Date().toUTCString() + const req = https.request({ + hostname, path: urlPath, method, + headers: { + Host: hostname, + Date: date, + Authorization: `OSS ${AK}:${sign(method, date, resource)}` + } + }, (res) => { + const chunks = [] + res.on('data', c => chunks.push(c)) + res.on('end', () => resolve({ status: res.statusCode, body: Buffer.concat(chunks).toString('utf8') })) + }) + req.on('error', reject) + req.end() +}) + +const probe = async (name, hostname, urlPath, resource) => { + try { + const r = await ossRequest('GET', hostname, urlPath, resource) + const codeMatch = r.body.match(/([^<]+)<\/Code>/) + const msgMatch = r.body.match(/([^<]+)<\/Message>/) + console.log(`[${name}] status=${r.status}${codeMatch ? ' code=' + codeMatch[1] : ''}${msgMatch ? ' msg=' + msgMatch[1].slice(0, 120) : ''}${r.status === 200 ? ' body前120=' + r.body.slice(0, 120).replace(/\s+/g, ' ') : ''}`) + return r + } catch (e) { + console.log(`[${name}] ERR ${e.message}`) + return null + } +} + +;(async () => { + await probe('ListBuckets(账号级)', ENDPOINT, '/', '/') + await probe('ListObjectsV2(prefix=trae-files-temp/)', `${BUCKET}.${ENDPOINT}`, '/?list-type=2&prefix=trae-files-temp%2F&max-keys=10', `/${BUCKET}/`) + await probe('ListObjectsV2(无prefix,根)', `${BUCKET}.${ENDPOINT}`, '/?list-type=2&max-keys=5', `/${BUCKET}/`) + await probe('GetBucketInfo', `${BUCKET}.${ENDPOINT}`, '/?bucketInfo', `/${BUCKET}/?bucketInfo`) +})().catch(e => { console.error(e); process.exit(1) }) diff --git a/dev_test_scripts/debug/debug_ossbrowser_e2e.js b/dev_test_scripts/debug/debug_ossbrowser_e2e.js new file mode 100644 index 0000000..60f1978 --- /dev/null +++ b/dev_test_scripts/debug/debug_ossbrowser_e2e.js @@ -0,0 +1,87 @@ +// 端到端实测:带包装层启动 oss-browser(CDP 远程调试),验证 +// 1) $rootScope.bucketMap['android-o1-images'].region === 'oss-cn-shanghai' +// 2) 文件列表真实加载出对象(页面文本含已知对象名) +// 3) 页面文本不含 AccessDenied +// 用法:node dev_test_scripts/debug/debug_ossbrowser_e2e.js +const { spawn } = require('child_process') +const path = require('path') + +const EXE = path.join(__dirname, '..', '..', 'temp', 'ossbrowser_inspect', 'oss-browser-win32-x64', 'oss-browser.exe') +const PORT = 9222 + +const sleep = ms => new Promise(r => setTimeout(r, ms)) + +const fetchJson = async url => { + const res = await fetch(url) + return res.json() +} + +const cdpEval = (ws, expression) => new Promise((resolve, reject) => { + const id = Math.floor(Math.random() * 1e6) + const onMsg = ev => { + try { + const msg = JSON.parse(ev.data) + if (msg.id === id) { + ws.removeEventListener('message', onMsg) + resolve(msg.result) + } + } catch {} + } + ws.addEventListener('message', onMsg) + ws.send(JSON.stringify({ id, method: 'Runtime.evaluate', params: { expression, returnByValue: true } })) + setTimeout(() => { ws.removeEventListener('message', onMsg); reject(new Error('eval timeout')) }, 8000) +}) + +const EXPRESSION = `(function () { + try { + var inj = window.angular && window.angular.element(document.documentElement).injector(); + var rs = inj && inj.get('$rootScope'); + var input = document.querySelector('input[ng-model="address"]'); + return JSON.stringify({ + bucketMap: rs ? rs.bucketMap : null, + address: input ? input.value : null, + dbg: window.__trae_dbg || null, + text: (document.body && document.body.innerText || '').slice(0, 6000) + }); + } catch (e) { return JSON.stringify({ err: String(e) }); } +})()` + +;(async () => { + const child = spawn(EXE, [`--remote-debugging-port=${PORT}`], { detached: true, stdio: 'ignore' }) + console.log('spawned pid', child.pid) + child.unref() + + let targets = null + for (let i = 0; i < 40 && !targets; i++) { + await sleep(1000) + try { + const list = await fetchJson(`http://127.0.0.1:${PORT}/json`) + const page = (list || []).find(t => t.type === 'page' && /index\.html/.test(t.url || '')) + if (page) targets = page + } catch {} + } + if (!targets) { console.log('[FAIL] 未找到 CDP page target'); process.exit(1) } + + const ws = new WebSocket(targets.webSocketDebuggerUrl) + await new Promise((res, rej) => { ws.onopen = res; ws.onerror = rej }) + + let ok = false + for (let i = 0; i < 20 && !ok; i++) { + await sleep(1500) + try { + const r = await cdpEval(ws, EXPRESSION) + const v = JSON.parse(r.result.value || '{}') + if (!v.bucketMap) continue + const region = v.bucketMap['android-o1-images'] && v.bucketMap['android-o1-images'].region + const hasFile = /jianpian\.rar|cover\.png/.test(v.text || '') + const denied = /AccessDenied/.test(v.text || '') + console.log(`probe#${i} address=${v.address} region=${region} hasFile=${hasFile} denied=${denied} dbg=${JSON.stringify(v.dbg)}`) + if (region === 'oss-cn-shanghai' && hasFile && !denied && v.address === 'oss://android-o1-images/trae-files-temp/') ok = true + } catch (e) { /* 页面可能还在加载 */ } + } + + ws.close() + try { process.kill(child.pid, 'SIGTERM') } catch {} + console.log(ok ? '[PASS] 地址栏定位 + region 正确 + 列表加载成功,无 AccessDenied' : '[FAIL] 未达到预期状态') + process.exit(ok ? 0 : 1) +})().catch(e => { console.error('[FAIL]', e.message); process.exit(1) }) diff --git a/dev_test_scripts/debug/debug_ossbrowser_probe.js b/dev_test_scripts/debug/debug_ossbrowser_probe.js new file mode 100644 index 0000000..69e95c9 --- /dev/null +++ b/dev_test_scripts/debug/debug_ossbrowser_probe.js @@ -0,0 +1,13 @@ +// 探查 login 流程 AuthInfo.save 的字段结构 +const fs = require('fs') +const s = fs.readFileSync('temp\\ossbrowser_inspect\\oss-browser-win32-x64\\resources\\app.asar').toString('latin1') +let i = -1, n = 0 +while ((i = s.indexOf('AuthInfo.save(', i + 1)) !== -1 && n < 6) { + console.log('--- hit ' + n) + console.log(s.slice(Math.max(0, i - 900), i + 200)) + n++ +} +// 再看 ossSvs2 如何取凭据构造 client +let j = s.indexOf("factory('ossSvs2'") +console.log('======== ossSvs2 head') +console.log(s.slice(j, j + 1500)) diff --git a/dev_test_scripts/debug/debug_verify_register_bat.js b/dev_test_scripts/debug/debug_verify_register_bat.js new file mode 100644 index 0000000..40c1e66 --- /dev/null +++ b/dev_test_scripts/debug/debug_verify_register_bat.js @@ -0,0 +1,29 @@ +// 校验 register_ossbrowser_launch.bat:关键行 + base64 回环解码与源码一致 +const fs = require('fs') +const path = require('path') +const { execFileSync } = require('child_process') + +const ROOT = path.join(__dirname, '..', '..') +const BAT = path.join(ROOT, 'public', 'tools', 'oss_browser_link', 'assets', 'register_ossbrowser_launch.bat') +const SRC = path.join(ROOT, 'public', 'tools', 'oss_browser_link', 'assets', 'ossbrowser_wrapper_main.js') + +const s = fs.readFileSync(BAT, 'utf8') +if (s.includes('\r\n') === false) { console.log('[FAIL] bat 不是 CRLF'); process.exit(1) } +const lines = s.split(/\r\n/) +console.log('total lines:', lines.length) +console.log('reg cmd line ok:', !!lines.find(l => l.includes('shell\\open\\command') && l.includes('\\"%EXE%\\"'))) +console.log('mkdir line ok:', !!lines.find(l => l.includes('mkdir "%~dp0resources\\app"'))) +console.log('txt region line ok:', !!lines.find(l => l.includes('echo oss-cn-shanghai>>'))) + +const marker = '>> "%~dp0_wrapper.b64"' +const b64 = lines.filter(l => l.startsWith('echo ') && l.endsWith(marker)).map(l => l.slice(5, l.length - marker.length)).join('') +console.log('b64 len:', b64.length) +const b64File = path.join(ROOT, 'temp', 'w2.b64') +const outFile = path.join(ROOT, 'temp', 'w2.js') +fs.writeFileSync(b64File, b64) +try { fs.unlinkSync(outFile) } catch {} +execFileSync('certutil', ['-decode', b64File, outFile], { stdio: 'pipe' }) +const a = fs.readFileSync(SRC) +const b = fs.readFileSync(outFile) +console.log('roundtrip identical:', a.equals(b)) +if (!a.equals(b)) process.exit(1) diff --git a/dev_test_scripts/tools/tool_build_wrapper_b64.js b/dev_test_scripts/tools/tool_build_wrapper_b64.js new file mode 100644 index 0000000..e1c9f99 --- /dev/null +++ b/dev_test_scripts/tools/tool_build_wrapper_b64.js @@ -0,0 +1,81 @@ +// 构建 register_ossbrowser_launch.bat:模板 + 包装层 base64 块 +// 源:public/tools/oss_browser_link/assets/ossbrowser_wrapper_main.js +// 产物:public/tools/oss_browser_link/assets/register_ossbrowser_launch.bat(CRLF) +const fs = require('fs') +const path = require('path') + +const ROOT = path.join(__dirname, '..', '..') +const SRC = path.join(ROOT, 'public', 'tools', 'oss_browser_link', 'assets', 'ossbrowser_wrapper_main.js') +const OUT = path.join(ROOT, 'public', 'tools', 'oss_browser_link', 'assets', 'register_ossbrowser_launch.bat') + +const b = fs.readFileSync(SRC).toString('base64') +const echoLines = [] +for (let i = 0; i < b.length; i += 96) { + echoLines.push(`echo ${b.slice(i, i + 96)}>> "%~dp0_wrapper.b64"`) +} + +const head = `@echo off +chcp 65001 >nul +setlocal +rem ========================================== +rem oss-browser 一键注册脚本(协议拉起 + 启动自动定位补丁) +rem 用法:把本文件放到与 oss-browser.exe 同一目录,双击运行 +rem 作用: +rem 1. 在当前用户注册表(HKCU)注册 ossbrowser:// 协议,无需管理员权限 +rem 2. 安装 resources\\app 包装层:启动时自动把默认 OSS 路径填进地址栏并定位(含正确 region) +rem 3. 生成 trae_default_oss_path.txt(第1行 oss 路径,第2行可选 region) +rem 卸载补丁:删除 resources\\app 目录与 trae_default_oss_path.txt 即恢复原版 +rem ========================================== +set "EXE=%~dp0oss-browser.exe" +if not exist "%EXE%" ( + echo. + echo [错误] 未在当前目录找到 oss-browser.exe + echo 请把本脚本放入解压后的 oss-browser-win32-x64 文件夹内(与 oss-browser.exe 同级)再运行。 + echo. + pause + exit /b 1 +) +reg add "HKCU\\Software\\Classes\\ossbrowser" /ve /d "URL:ossbrowser Protocol" /f >nul +reg add "HKCU\\Software\\Classes\\ossbrowser" /v "URL Protocol" /t REG_SZ /d "" /f >nul +reg add "HKCU\\Software\\Classes\\ossbrowser\\shell\\open\\command" /ve /d "\\"%EXE%\\" \\"%%1\\"" /f >nul +if errorlevel 1 ( + echo [失败] 注册表写入出错。 + pause + exit /b 1 +) +echo [完成] 已注册 ossbrowser:// 协议。 + +rem ---- 安装启动自动定位包装层 ---- +if not exist "%~dp0resources\\app" mkdir "%~dp0resources\\app" +echo {"name":"oss-browser","version":"1.18.0","main":"main.js"}> "%~dp0resources\\app\\package.json" +if exist "%~dp0_wrapper.b64" del "%~dp0_wrapper.b64" >nul +` + +const tail = `if exist "%~dp0resources\\app\\main.js" del "%~dp0resources\\app\\main.js" >nul +certutil -decode "%~dp0_wrapper.b64" "%~dp0resources\\app\\main.js" >nul +if errorlevel 1 ( + echo [警告] 包装层解码失败,跳过自动定位补丁(协议拉起仍可用)。 +) else ( + echo [完成] 已安装启动自动定位包装层(resources\\app)。 +) +del "%~dp0_wrapper.b64" >nul + +rem ---- 默认路径配置(已存在则不覆盖,尊重手动修改) ---- +if not exist "%~dp0trae_default_oss_path.txt" ( + echo oss://android-o1-images/trae-files-temp/> "%~dp0trae_default_oss_path.txt" + echo oss-cn-shanghai>> "%~dp0trae_default_oss_path.txt" + echo [完成] 已生成默认路径配置 trae_default_oss_path.txt +) + +echo. +echo 现在可回到网页点击「打开 oss-browser」。 +echo 首次拉起时浏览器会弹确认框,勾选“始终允许”后即为无感打开。 +echo 启动后地址栏会自动定位到 trae_default_oss_path.txt 中的路径。 +echo 该文件第1行为 oss 路径,第2行可选 region(默认 oss-cn-shanghai)。 +echo. +pause +` + +const bat = (head + echoLines.join('\r\n') + '\r\n' + tail).replace(/\r\n/g, '\n').replace(/\n/g, '\r\n') +fs.writeFileSync(OUT, bat) +console.log('bat written, echo lines =', echoLines.length) diff --git a/public/tools/oss_browser_link/assets/ossbrowser_wrapper_main.js b/public/tools/oss_browser_link/assets/ossbrowser_wrapper_main.js new file mode 100644 index 0000000..b17f4f3 --- /dev/null +++ b/public/tools/oss_browser_link/assets/ossbrowser_wrapper_main.js @@ -0,0 +1,101 @@ +// Trae-Toolbox oss-browser 包装层(resources/app/main.js) +// 作用:启动时读取 exe 同目录 trae_default_oss_path.txt(第1行 oss 路径,第2行可选 region), +// 并从本机 ~/Toolbox_local_creds.env.local 读取 DOC_CLOUD_KEEPER_OSS_* 凭据(运行时读取,零硬编码), +// 注入渲染进程:写入 AuthInfo 并补丁 $rootScope.currentAuthInfo / bucketMap, +// 使 oss-browser 每次启动即以有效账号登录、地址栏自动定位到默认目录; +// 随后原样交接给官方 app.asar 内的 main.js,不改动官方包任何字节。 +// 卸载:删除 resources\app 目录与 trae_default_oss_path.txt 即恢复原版。 +const { app } = require('electron') +const fs = require('fs') +const os = require('os') +const path = require('path') + +const readDefaultConfig = () => { + const cfg = { target: '', region: 'oss-cn-shanghai', id: '', secret: '' } + try { + const p = path.join(path.dirname(process.execPath), 'trae_default_oss_path.txt') + if (fs.existsSync(p)) { + const lines = String(fs.readFileSync(p, 'utf8')).split(/\r?\n/).map(l => l.trim()).filter(Boolean) + if (lines[0] && lines[0].indexOf('oss://') === 0) cfg.target = lines[0] + if (lines[1] && /^oss-[a-z0-9-]+$/.test(lines[1])) cfg.region = lines[1] + } + } catch (e) {} + try { + const p = path.join(os.homedir(), 'Toolbox_local_creds.env.local') + if (fs.existsSync(p)) { + const text = String(fs.readFileSync(p, 'utf8')) + for (const line of text.split(/\r?\n/)) { + const t = line.trim() + if (!t || t.startsWith('#')) continue + const eq = t.indexOf('=') + if (eq <= 0) continue + const k = t.slice(0, eq).trim() + const v = t.slice(eq + 1).trim() + if (k === 'DOC_CLOUD_KEEPER_OSS_ACCESS_KEY_ID') cfg.id = v + if (k === 'DOC_CLOUD_KEEPER_OSS_ACCESS_KEY_SECRET') cfg.secret = v + } + } + } catch (e) {} + return cfg +} + +const CFG = readDefaultConfig() + +if (CFG.target) { + const credPatch = (CFG.id && CFG.secret) + ? { id: CFG.id, secret: CFG.secret, eptpl: 'https://{region}.aliyuncs.com' } + : null + const injection = + '(function () {' + + ' var TARGET = ' + JSON.stringify(CFG.target) + ';' + + ' var REGION = ' + JSON.stringify(CFG.region) + ';' + + ' var CRED = ' + JSON.stringify(credPatch) + ';' + + ' var BUCKET = TARGET.split("/")[2];' + + ' var seeded = false;' + + ' var broadcasts = 0;' + + ' var tries = 0;' + + ' var timer = setInterval(function () {' + + ' tries++;' + + ' if (tries > 80) { clearInterval(timer); return; }' + + ' try {' + + ' var ng = window.angular;' + + ' if (!ng) return;' + + ' var el = document.querySelector("[ng-app]") || document.documentElement;' + + ' var inj = ng.element(el).injector();' + + ' if (!inj) return;' + + ' if (!seeded) {' + + ' seeded = true;' + + ' var patch = { osspath: TARGET, address: TARGET, region: REGION };' + + ' if (CRED) { patch.id = CRED.id; patch.secret = CRED.secret; patch.eptpl = CRED.eptpl; }' + + ' inj.get("AuthInfo").saveToAuthInfo(patch);' + + ' var rs = inj.get("$rootScope");' + + ' rs.currentAuthInfo = rs.currentAuthInfo || {};' + + ' for (var k in patch) { rs.currentAuthInfo[k] = patch[k]; }' + + ' rs.internalSupported = false;' + + ' rs.bucketMap = rs.bucketMap || {};' + + ' rs.bucketMap[BUCKET] = { region: REGION };' + + ' }' + + ' if (broadcasts < 8) { broadcasts++; inj.get("$rootScope").$broadcast("goToOssAddress", TARGET); }' + + ' else { clearInterval(timer); }' + + ' } catch (e) {}' + + ' }, 500);' + + '})();' + + app.on('web-contents-created', function (event, contents) { + contents.on('did-finish-load', function () { + try { contents.executeJavaScript(injection) } catch (e) {} + }) + }) +} + +// 交接给官方 oss-browser 主进程逻辑 +try { + require(path.join(__dirname, '..', 'app.asar', 'main.js')) +} catch (e) { + try { + require('electron').dialog.showErrorBox( + 'oss-browser 包装层错误', + String((e && e.message) || e) + '\n如需恢复原版,请删除 resources\\app 目录。' + ) + } catch (e2) {} +} diff --git a/public/tools/oss_browser_link/assets/register_ossbrowser_launch.bat b/public/tools/oss_browser_link/assets/register_ossbrowser_launch.bat index 6fa63dc..99f1129 100644 --- a/public/tools/oss_browser_link/assets/register_ossbrowser_launch.bat +++ b/public/tools/oss_browser_link/assets/register_ossbrowser_launch.bat @@ -2,9 +2,13 @@ chcp 65001 >nul setlocal rem ========================================== -rem oss-browser 一键拉起协议注册脚本 +rem oss-browser 一键注册脚本(协议拉起 + 启动自动定位补丁) rem 用法:把本文件放到与 oss-browser.exe 同一目录,双击运行 -rem 作用:在当前用户注册表(HKCU)注册 ossbrowser:// 协议,无需管理员权限 +rem 作用: +rem 1. 在当前用户注册表(HKCU)注册 ossbrowser:// 协议,无需管理员权限 +rem 2. 安装 resources\app 包装层:启动时自动把默认 OSS 路径填进地址栏并定位(含正确 region) +rem 3. 生成 trae_default_oss_path.txt(第1行 oss 路径,第2行可选 region) +rem 卸载补丁:删除 resources\app 目录与 trae_default_oss_path.txt 即恢复原版 rem ========================================== set "EXE=%~dp0oss-browser.exe" if not exist "%EXE%" ( @@ -23,9 +27,92 @@ if errorlevel 1 ( pause exit /b 1 ) +echo [完成] 已注册 ossbrowser:// 协议。 + +rem ---- 安装启动自动定位包装层 ---- +if not exist "%~dp0resources\app" mkdir "%~dp0resources\app" +echo {"name":"oss-browser","version":"1.18.0","main":"main.js"}> "%~dp0resources\app\package.json" +if exist "%~dp0_wrapper.b64" del "%~dp0_wrapper.b64" >nul +echo Ly8gVHJhZS1Ub29sYm94IG9zcy1icm93c2VyIOWMheijheWxgu+8iHJlc291cmNlcy9hcHAvbWFpbi5qc++8iQovLyDkvZzn>> "%~dp0_wrapper.b64" +echo lKjvvJrlkK/liqjml7bor7vlj5YgZXhlIOWQjOebruW9lSB0cmFlX2RlZmF1bHRfb3NzX3BhdGgudHh077yI56ysMeihjCBv>> "%~dp0_wrapper.b64" +echo c3Mg6Lev5b6E77yM56ysMuihjOWPr+mAiSByZWdpb27vvInvvIwKLy8gICAgICAg5bm25LuO5pys5py6IH4vVG9vbGJveF9s>> "%~dp0_wrapper.b64" +echo b2NhbF9jcmVkcy5lbnYubG9jYWwg6K+75Y+WIERPQ19DTE9VRF9LRUVQRVJfT1NTXyog5Yet5o2u77yI6L+Q6KGM5pe26K+7>> "%~dp0_wrapper.b64" +echo 5Y+W77yM6Zu256Gs57yW56CB77yJ77yMCi8vICAgICAgIOazqOWFpea4suafk+i/m+eoi++8muWGmeWFpSBBdXRoSW5mbyDl>> "%~dp0_wrapper.b64" +echo ubbooaXkuIEgJHJvb3RTY29wZS5jdXJyZW50QXV0aEluZm8gLyBidWNrZXRNYXDvvIwKLy8gICAgICAg5L2/IG9zcy1icm93>> "%~dp0_wrapper.b64" +echo c2VyIOavj+asoeWQr+WKqOWNs+S7peacieaViOi0puWPt+eZu+W9leOAgeWcsOWdgOagj+iHquWKqOWumuS9jeWIsOm7mOiu>> "%~dp0_wrapper.b64" +echo pOebruW9le+8mwovLyAgICAgICDpmo/lkI7ljp/moLfkuqTmjqXnu5nlrpjmlrkgYXBwLmFzYXIg5YaF55qEIG1haW4uanPv>> "%~dp0_wrapper.b64" +echo vIzkuI3mlLnliqjlrpjmlrnljIXku7vkvZXlrZfoioLjgIIKLy8g5Y246L2977ya5Yig6ZmkIHJlc291cmNlc1xhcHAg55uu>> "%~dp0_wrapper.b64" +echo 5b2V5LiOIHRyYWVfZGVmYXVsdF9vc3NfcGF0aC50eHQg5Y2z5oGi5aSN5Y6f54mI44CCCmNvbnN0IHsgYXBwIH0gPSByZXF1>> "%~dp0_wrapper.b64" +echo aXJlKCdlbGVjdHJvbicpCmNvbnN0IGZzID0gcmVxdWlyZSgnZnMnKQpjb25zdCBvcyA9IHJlcXVpcmUoJ29zJykKY29uc3Qg>> "%~dp0_wrapper.b64" +echo cGF0aCA9IHJlcXVpcmUoJ3BhdGgnKQoKY29uc3QgcmVhZERlZmF1bHRDb25maWcgPSAoKSA9PiB7CiAgY29uc3QgY2ZnID0g>> "%~dp0_wrapper.b64" +echo eyB0YXJnZXQ6ICcnLCByZWdpb246ICdvc3MtY24tc2hhbmdoYWknLCBpZDogJycsIHNlY3JldDogJycgfQogIHRyeSB7CiAg>> "%~dp0_wrapper.b64" +echo ICBjb25zdCBwID0gcGF0aC5qb2luKHBhdGguZGlybmFtZShwcm9jZXNzLmV4ZWNQYXRoKSwgJ3RyYWVfZGVmYXVsdF9vc3Nf>> "%~dp0_wrapper.b64" +echo cGF0aC50eHQnKQogICAgaWYgKGZzLmV4aXN0c1N5bmMocCkpIHsKICAgICAgY29uc3QgbGluZXMgPSBTdHJpbmcoZnMucmVh>> "%~dp0_wrapper.b64" +echo ZEZpbGVTeW5jKHAsICd1dGY4JykpLnNwbGl0KC9ccj9cbi8pLm1hcChsID0+IGwudHJpbSgpKS5maWx0ZXIoQm9vbGVhbikK>> "%~dp0_wrapper.b64" +echo ICAgICAgaWYgKGxpbmVzWzBdICYmIGxpbmVzWzBdLmluZGV4T2YoJ29zczovLycpID09PSAwKSBjZmcudGFyZ2V0ID0gbGlu>> "%~dp0_wrapper.b64" +echo ZXNbMF0KICAgICAgaWYgKGxpbmVzWzFdICYmIC9eb3NzLVthLXowLTktXSskLy50ZXN0KGxpbmVzWzFdKSkgY2ZnLnJlZ2lv>> "%~dp0_wrapper.b64" +echo biA9IGxpbmVzWzFdCiAgICB9CiAgfSBjYXRjaCAoZSkge30KICB0cnkgewogICAgY29uc3QgcCA9IHBhdGguam9pbihvcy5o>> "%~dp0_wrapper.b64" +echo b21lZGlyKCksICdUb29sYm94X2xvY2FsX2NyZWRzLmVudi5sb2NhbCcpCiAgICBpZiAoZnMuZXhpc3RzU3luYyhwKSkgewog>> "%~dp0_wrapper.b64" +echo ICAgICBjb25zdCB0ZXh0ID0gU3RyaW5nKGZzLnJlYWRGaWxlU3luYyhwLCAndXRmOCcpKQogICAgICBmb3IgKGNvbnN0IGxp>> "%~dp0_wrapper.b64" +echo bmUgb2YgdGV4dC5zcGxpdCgvXHI/XG4vKSkgewogICAgICAgIGNvbnN0IHQgPSBsaW5lLnRyaW0oKQogICAgICAgIGlmICgh>> "%~dp0_wrapper.b64" +echo dCB8fCB0LnN0YXJ0c1dpdGgoJyMnKSkgY29udGludWUKICAgICAgICBjb25zdCBlcSA9IHQuaW5kZXhPZignPScpCiAgICAg>> "%~dp0_wrapper.b64" +echo ICAgaWYgKGVxIDw9IDApIGNvbnRpbnVlCiAgICAgICAgY29uc3QgayA9IHQuc2xpY2UoMCwgZXEpLnRyaW0oKQogICAgICAg>> "%~dp0_wrapper.b64" +echo IGNvbnN0IHYgPSB0LnNsaWNlKGVxICsgMSkudHJpbSgpCiAgICAgICAgaWYgKGsgPT09ICdET0NfQ0xPVURfS0VFUEVSX09T>> "%~dp0_wrapper.b64" +echo U19BQ0NFU1NfS0VZX0lEJykgY2ZnLmlkID0gdgogICAgICAgIGlmIChrID09PSAnRE9DX0NMT1VEX0tFRVBFUl9PU1NfQUND>> "%~dp0_wrapper.b64" +echo RVNTX0tFWV9TRUNSRVQnKSBjZmcuc2VjcmV0ID0gdgogICAgICB9CiAgICB9CiAgfSBjYXRjaCAoZSkge30KICByZXR1cm4g>> "%~dp0_wrapper.b64" +echo Y2ZnCn0KCmNvbnN0IENGRyA9IHJlYWREZWZhdWx0Q29uZmlnKCkKCmlmIChDRkcudGFyZ2V0KSB7CiAgY29uc3QgY3JlZFBh>> "%~dp0_wrapper.b64" +echo dGNoID0gKENGRy5pZCAmJiBDRkcuc2VjcmV0KQogICAgPyB7IGlkOiBDRkcuaWQsIHNlY3JldDogQ0ZHLnNlY3JldCwgZXB0>> "%~dp0_wrapper.b64" +echo cGw6ICdodHRwczovL3tyZWdpb259LmFsaXl1bmNzLmNvbScgfQogICAgOiBudWxsCiAgY29uc3QgaW5qZWN0aW9uID0KICAg>> "%~dp0_wrapper.b64" +echo ICcoZnVuY3Rpb24gKCkgeycgKwogICAgJyAgdmFyIFRBUkdFVCA9ICcgKyBKU09OLnN0cmluZ2lmeShDRkcudGFyZ2V0KSAr>> "%~dp0_wrapper.b64" +echo ICc7JyArCiAgICAnICB2YXIgUkVHSU9OID0gJyArIEpTT04uc3RyaW5naWZ5KENGRy5yZWdpb24pICsgJzsnICsKICAgICcg>> "%~dp0_wrapper.b64" +echo IHZhciBDUkVEID0gJyArIEpTT04uc3RyaW5naWZ5KGNyZWRQYXRjaCkgKyAnOycgKwogICAgJyAgdmFyIEJVQ0tFVCA9IFRB>> "%~dp0_wrapper.b64" +echo UkdFVC5zcGxpdCgiLyIpWzJdOycgKwogICAgJyAgdmFyIHNlZWRlZCA9IGZhbHNlOycgKwogICAgJyAgdmFyIGJyb2FkY2Fz>> "%~dp0_wrapper.b64" +echo dHMgPSAwOycgKwogICAgJyAgdmFyIHRyaWVzID0gMDsnICsKICAgICcgIHZhciB0aW1lciA9IHNldEludGVydmFsKGZ1bmN0>> "%~dp0_wrapper.b64" +echo aW9uICgpIHsnICsKICAgICcgICAgdHJpZXMrKzsnICsKICAgICcgICAgaWYgKHRyaWVzID4gODApIHsgY2xlYXJJbnRlcnZh>> "%~dp0_wrapper.b64" +echo bCh0aW1lcik7IHJldHVybjsgfScgKwogICAgJyAgICB0cnkgeycgKwogICAgJyAgICAgIHZhciBuZyA9IHdpbmRvdy5hbmd1>> "%~dp0_wrapper.b64" +echo bGFyOycgKwogICAgJyAgICAgIGlmICghbmcpIHJldHVybjsnICsKICAgICcgICAgICB2YXIgZWwgPSBkb2N1bWVudC5xdWVy>> "%~dp0_wrapper.b64" +echo eVNlbGVjdG9yKCJbbmctYXBwXSIpIHx8IGRvY3VtZW50LmRvY3VtZW50RWxlbWVudDsnICsKICAgICcgICAgICB2YXIgaW5q>> "%~dp0_wrapper.b64" +echo ID0gbmcuZWxlbWVudChlbCkuaW5qZWN0b3IoKTsnICsKICAgICcgICAgICBpZiAoIWluaikgcmV0dXJuOycgKwogICAgJyAg>> "%~dp0_wrapper.b64" +echo ICAgIGlmICghc2VlZGVkKSB7JyArCiAgICAnICAgICAgICBzZWVkZWQgPSB0cnVlOycgKwogICAgJyAgICAgICAgdmFyIHBh>> "%~dp0_wrapper.b64" +echo dGNoID0geyBvc3NwYXRoOiBUQVJHRVQsIGFkZHJlc3M6IFRBUkdFVCwgcmVnaW9uOiBSRUdJT04gfTsnICsKICAgICcgICAg>> "%~dp0_wrapper.b64" +echo ICAgIGlmIChDUkVEKSB7IHBhdGNoLmlkID0gQ1JFRC5pZDsgcGF0Y2guc2VjcmV0ID0gQ1JFRC5zZWNyZXQ7IHBhdGNoLmVw>> "%~dp0_wrapper.b64" +echo dHBsID0gQ1JFRC5lcHRwbDsgfScgKwogICAgJyAgICAgICAgaW5qLmdldCgiQXV0aEluZm8iKS5zYXZlVG9BdXRoSW5mbyhw>> "%~dp0_wrapper.b64" +echo YXRjaCk7JyArCiAgICAnICAgICAgICB2YXIgcnMgPSBpbmouZ2V0KCIkcm9vdFNjb3BlIik7JyArCiAgICAnICAgICAgICBy>> "%~dp0_wrapper.b64" +echo cy5jdXJyZW50QXV0aEluZm8gPSBycy5jdXJyZW50QXV0aEluZm8gfHwge307JyArCiAgICAnICAgICAgICBmb3IgKHZhciBr>> "%~dp0_wrapper.b64" +echo IGluIHBhdGNoKSB7IHJzLmN1cnJlbnRBdXRoSW5mb1trXSA9IHBhdGNoW2tdOyB9JyArCiAgICAnICAgICAgICBycy5pbnRl>> "%~dp0_wrapper.b64" +echo cm5hbFN1cHBvcnRlZCA9IGZhbHNlOycgKwogICAgJyAgICAgICAgcnMuYnVja2V0TWFwID0gcnMuYnVja2V0TWFwIHx8IHt9>> "%~dp0_wrapper.b64" +echo OycgKwogICAgJyAgICAgICAgcnMuYnVja2V0TWFwW0JVQ0tFVF0gPSB7IHJlZ2lvbjogUkVHSU9OIH07JyArCiAgICAnICAg>> "%~dp0_wrapper.b64" +echo ICAgfScgKwogICAgJyAgICAgIGlmIChicm9hZGNhc3RzIDwgOCkgeyBicm9hZGNhc3RzKys7IGluai5nZXQoIiRyb290U2Nv>> "%~dp0_wrapper.b64" +echo cGUiKS4kYnJvYWRjYXN0KCJnb1RvT3NzQWRkcmVzcyIsIFRBUkdFVCk7IH0nICsKICAgICcgICAgICBlbHNlIHsgY2xlYXJJ>> "%~dp0_wrapper.b64" +echo bnRlcnZhbCh0aW1lcik7IH0nICsKICAgICcgICAgfSBjYXRjaCAoZSkge30nICsKICAgICcgIH0sIDUwMCk7JyArCiAgICAn>> "%~dp0_wrapper.b64" +echo fSkoKTsnCgogIGFwcC5vbignd2ViLWNvbnRlbnRzLWNyZWF0ZWQnLCBmdW5jdGlvbiAoZXZlbnQsIGNvbnRlbnRzKSB7CiAg>> "%~dp0_wrapper.b64" +echo ICBjb250ZW50cy5vbignZGlkLWZpbmlzaC1sb2FkJywgZnVuY3Rpb24gKCkgewogICAgICB0cnkgeyBjb250ZW50cy5leGVj>> "%~dp0_wrapper.b64" +echo dXRlSmF2YVNjcmlwdChpbmplY3Rpb24pIH0gY2F0Y2ggKGUpIHt9CiAgICB9KQogIH0pCn0KCi8vIOS6pOaOpee7meWumOaW>> "%~dp0_wrapper.b64" +echo uSBvc3MtYnJvd3NlciDkuLvov5vnqIvpgLvovpEKdHJ5IHsKICByZXF1aXJlKHBhdGguam9pbihfX2Rpcm5hbWUsICcuLics>> "%~dp0_wrapper.b64" +echo ICdhcHAuYXNhcicsICdtYWluLmpzJykpCn0gY2F0Y2ggKGUpIHsKICB0cnkgewogICAgcmVxdWlyZSgnZWxlY3Ryb24nKS5k>> "%~dp0_wrapper.b64" +echo aWFsb2cuc2hvd0Vycm9yQm94KAogICAgICAnb3NzLWJyb3dzZXIg5YyF6KOF5bGC6ZSZ6K+vJywKICAgICAgU3RyaW5nKChl>> "%~dp0_wrapper.b64" +echo ICYmIGUubWVzc2FnZSkgfHwgZSkgKyAnXG7lpoLpnIDmgaLlpI3ljp/niYjvvIzor7fliKDpmaQgcmVzb3VyY2VzXFxhcHAg>> "%~dp0_wrapper.b64" +echo 55uu5b2V44CCJwogICAgKQogIH0gY2F0Y2ggKGUyKSB7fQp9Cg==>> "%~dp0_wrapper.b64" +if exist "%~dp0resources\app\main.js" del "%~dp0resources\app\main.js" >nul +certutil -decode "%~dp0_wrapper.b64" "%~dp0resources\app\main.js" >nul +if errorlevel 1 ( + echo [警告] 包装层解码失败,跳过自动定位补丁(协议拉起仍可用)。 +) else ( + echo [完成] 已安装启动自动定位包装层(resources\app)。 +) +del "%~dp0_wrapper.b64" >nul + +rem ---- 默认路径配置(已存在则不覆盖,尊重手动修改) ---- +if not exist "%~dp0trae_default_oss_path.txt" ( + echo oss://android-o1-images/trae-files-temp/> "%~dp0trae_default_oss_path.txt" + echo oss-cn-shanghai>> "%~dp0trae_default_oss_path.txt" + echo [完成] 已生成默认路径配置 trae_default_oss_path.txt +) + echo. -echo [完成] 已注册 ossbrowser:// 协议,指向: -echo %EXE% -echo 现在可回到网页点击「打开 oss-browser」。首次拉起时浏览器会弹确认框,勾选“始终允许”后即为无感打开。 +echo 现在可回到网页点击「打开 oss-browser」。 +echo 首次拉起时浏览器会弹确认框,勾选“始终允许”后即为无感打开。 +echo 启动后地址栏会自动定位到 trae_default_oss_path.txt 中的路径。 +echo 该文件第1行为 oss 路径,第2行可选 region(默认 oss-cn-shanghai)。 echo. pause diff --git a/public/tools/oss_browser_link/index.html b/public/tools/oss_browser_link/index.html index f029808..b826c34 100644 --- a/public/tools/oss_browser_link/index.html +++ b/public/tools/oss_browser_link/index.html @@ -30,7 +30,7 @@
- +
@@ -39,12 +39,12 @@
  • 下载:点击上方「下载」获取安装包(直链本身受 Toolbox 鉴权保护,未授权无法下载)。
  • 解压:将 zip 解压到一个固定目录(如 D:\oss-browser)。
  • 注册协议(仅首次):下载「一键注册脚本」,放入解压目录(与 oss-browser.exe 同级),双击运行。脚本会在当前用户注册表注册 ossbrowser:// 协议,无需管理员权限。
  • +
  • 启动自动定位 + 免登录:注册脚本同时安装包装层补丁,之后每次启动 oss-browser 会自动以本机 Toolbox 配置的 OSS 账号登录,地址栏自动填入并定位到默认路径,无需手动输入。
  • +
  • 改默认路径:编辑解压目录下的 trae_default_oss_path.txt(第1行 oss:// 路径,第2行可选 region,默认 oss-cn-shanghai)。
  • +
  • 升级过旧版脚本的:请重新下载本注册脚本并再次双击运行(会覆盖包装层),否则自动定位可能因缺少 region 而报 403。
  • +
  • 卸载补丁:删除 resources\app 目录与 trae_default_oss_path.txt 即恢复原版。
  • 一键打开:以后点击「打开 oss-browser」即可直接拉起本地程序(浏览器首次会弹确认框,勾选"始终允许"后不再打扰)。
  • 若检测到未安装,会自动回退为下载安装包。
  • - -
  • 阿里OSS直链中转。DOC_CLOUD_KEEPER_OSS_ACCESS_KEY_ID是LTAI5t9fraTXxm-dafe-TwE31Ph2ne
  • -
  • DOC_CLOUD_KEEPER_OSS_ACCESS_KEY_SECRET是LBN3ntJfdwlnmCiA9QCed-dafe-Y7XlhxpvX
  • -
  • 路径是oss://android-o1-images/trae-dafe-files-temp/
  • diff --git a/public/tools/private_clipboard/clipboard.js b/public/tools/private_clipboard/clipboard.js index 53f629a..57b2601 100644 --- a/public/tools/private_clipboard/clipboard.js +++ b/public/tools/private_clipboard/clipboard.js @@ -893,8 +893,16 @@ const getStringFromItem = (item, timeoutMs) => new Promise((resolve) => { } }) +const isEditableTarget = (el) => { + if (!el || el.nodeType !== 1) return false + const tag = String(el.tagName || '').toUpperCase() + return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || !!el.isContentEditable +} + const handlePaste = async (event) => { if (!isQuickEntryMode()) return + // 焦点在输入框/文本域内时,交还给浏览器默认粘贴,不做全局捕获 + if (isEditableTarget(event.target) || isEditableTarget(document.activeElement)) return const data = event.clipboardData if (!data) return const items = Array.from(data.items || [])