feat: 按任务管理比特浏览器临时窗口

This commit is contained in:
2026-09-23 18:15:08 +08:00
parent 04309f60c7
commit ff2879f102
15 changed files with 577 additions and 226 deletions
+91
View File
@@ -0,0 +1,91 @@
'use strict';
const assert = require('assert');
const { TaskBrowserLifecycle } = require('../src/services/task_browser_lifecycle');
function createHarness(initialBrowsers = []) {
const order = [];
const notifications = [];
const rows = new Map();
let createCount = 0;
const lifecycle = new TaskBrowserLifecycle({
getBrowserListSimple: async () => initialBrowsers,
createBrowserFromAccount: async account => {
createCount++;
order.push(`create:${account.email}`);
return { browserId: `created-${createCount}`, browserSeq: createCount };
},
getApi: () => ({
updateBrowserProxy: async ids => {
order.push(`proxy:${ids[0]}`);
return { success: true };
},
getAliveBrowserPids: async () => ({ success: true, data: {} }),
}),
closeBrowser: async id => { order.push(`close:${id}`); return { success: true }; },
deleteBrowsersBatch: async (ids, callback) => {
order.push(`delete:${ids[0]}`);
callback(ids[0], true, '');
return { successCount: 1, total: 1 };
},
parseAccountProxy: value => (value
? { proxyMethod: 2, proxyType: 'socks5', host: '127.0.0.1', port: '1080' }
: { proxyMethod: 2, proxyType: 'noproxy' }),
dbWrite: async (fn, args) => {
if (fn === 'updateAccountBrowserId') {
const previous = rows.get(args[0]) || { email: args[0] };
rows.set(args[0], { ...previous, browser_id: args[1] || '', browser_seq: args[2] ?? null });
order.push(`db:${args[0]}:${args[1] || 'clear'}`);
return true;
}
if (fn === 'getAccount') return rows.get(args[0]) || null;
throw new Error(`unexpected db call: ${fn}`);
},
updateMessage: async () => {},
acquireRequestSlot: async () => { order.push('slot'); },
notify: message => notifications.push(message),
});
return { lifecycle, order, notifications, rows, getCreateCount: () => createCount };
}
(async () => {
{
const harness = createHarness([{ id: 'bit-existing', name: 'user@example.com', seq: 7 }]);
const account = { email: 'user@example.com', browser_id: 'stale-id', proxy_ip: '127.0.0.1:1080' };
const result = await harness.lifecycle.run(account, async prepared => {
harness.order.push(`work:${prepared.browser_id}`);
return { ok: true };
});
assert.deepStrictEqual(result, { ok: true });
assert.strictEqual(harness.getCreateCount(), 0, '同邮箱比特窗口应复用,旧 ID 不应阻止重绑');
assert(harness.order.indexOf('proxy:bit-existing') < harness.order.indexOf('work:bit-existing'));
assert(harness.order.indexOf('work:bit-existing') < harness.order.indexOf('delete:bit-existing'));
assert.strictEqual(account.browser_id, '', '任务结束后应清空账号浏览器绑定');
assert.strictEqual(harness.rows.get(account.email).browser_id, '');
assert.deepStrictEqual(harness.notifications.map(item => item.action), ['prepared', 'released']);
}
{
const harness = createHarness([]);
const account = { email: 'new@example.com', proxy_ip: '' };
const first = await harness.lifecycle.run(account, async prepared => ({
__defer: true,
browserId: prepared.browser_id,
}));
assert.strictEqual(first.__defer, true);
assert.strictEqual(account.browser_id, 'created-1', '延迟重试仍属于同一账号任务,不应提前删除窗口');
assert.strictEqual(harness.order.filter(item => item.startsWith('delete:')).length, 0);
const second = await harness.lifecycle.run(account, async prepared => ({ browserId: prepared.browser_id }));
assert.strictEqual(second.browserId, 'created-1');
assert.strictEqual(harness.getCreateCount(), 1, '延迟重试应继续复用当前临时窗口');
assert.strictEqual(account.browser_id, '');
assert.strictEqual(harness.order.filter(item => item === 'delete:created-1').length, 1);
}
console.log('task_browser_lifecycle.test.js PASS');
})().catch(error => {
console.error(error);
process.exitCode = 1;
});