This commit is contained in:
zhu
2026-04-30 11:03:26 +08:00
parent 48ce6a8b0b
commit 08a6a69bd6
9 changed files with 566 additions and 42 deletions

View File

@@ -1,4 +1,8 @@
import type { BackgroundCommand } from './types';
import { getPlatformById } from '@/config/platforms';
import type { CrawlProgressStep, CrawlTaskState } from '@/types';
import type { BackgroundCommand, BackgroundResponse, CrawlStateResponse } from './types';
const CRAWL_TASK_STORAGE_KEY = 'crawlTaskState';
export async function handleInstalled(): Promise<void> {
console.log('[background] installed');
@@ -10,9 +14,148 @@ export async function handleStartup(): Promise<void> {
export async function handleWindowRemoved(windowId: number): Promise<void> {
console.log('[background] window removed', windowId);
const state = await getCrawlTaskState();
if (state?.windowId === windowId && state.status === 'running') {
await setCrawlTaskState({
...state,
status: 'canceled',
steps: state.steps.map((step, index) =>
index === state.currentStepIndex ? { ...step, status: 'failed', message: '爬取窗口已关闭' } : step,
),
});
}
}
export async function handleBackgroundCommand(message: BackgroundCommand): Promise<unknown> {
console.log('[background] message', message);
return { ok: true };
export async function handleBackgroundCommand(
message: BackgroundCommand,
): Promise<BackgroundResponse | CrawlStateResponse> {
switch (message.action) {
case 'START_CRAWL':
return startCrawl(message.payload.platformId);
case 'GET_CRAWL_STATE':
return { ok: true, data: await getCrawlTaskState() };
case 'CANCEL_CRAWL':
return cancelCrawl();
default:
return { ok: false, error: '未知的后台指令' };
}
}
async function startCrawl(platformId: string): Promise<CrawlStateResponse> {
const platform = getPlatformById(platformId);
if (!platform) {
return { ok: false, error: '平台配置不存在' };
}
const startedAt = Date.now();
const nextState: CrawlTaskState = {
id: `${platform.id}-${startedAt}`,
platformId: platform.id,
platformName: platform.name,
startedAt,
status: 'running',
currentStepIndex: 0,
steps: platform.steps.map<CrawlProgressStep>((step, index) => ({
name: step.name,
uniqueKey: step.uniqueKey,
status: index === 0 ? 'running' : 'pending',
})),
};
await setCrawlTaskState(nextState);
try {
const windowInfo = await createCrawlWindow(platform.baseUrl);
const stateWithWindow = { ...nextState, windowId: windowInfo.id };
await setCrawlTaskState(stateWithWindow);
return { ok: true, data: stateWithWindow };
} catch (error: unknown) {
const failedState: CrawlTaskState = {
...nextState,
status: 'failed',
steps: nextState.steps.map((step, index) =>
index === 0 ? { ...step, status: 'failed', message: '打开平台窗口失败' } : step,
),
};
await setCrawlTaskState(failedState);
return { ok: false, data: failedState, error: error instanceof Error ? error.message : '打开平台窗口失败' };
}
}
async function cancelCrawl(): Promise<CrawlStateResponse> {
const state = await getCrawlTaskState();
if (!state) {
return { ok: true, data: null };
}
const canceledState: CrawlTaskState = {
...state,
status: 'canceled',
steps: state.steps.map((step, index) =>
index === state.currentStepIndex ? { ...step, status: 'failed', message: '用户已取消' } : step,
),
};
await setCrawlTaskState(canceledState);
if (state.windowId) {
await removeWindow(state.windowId);
}
return { ok: true, data: canceledState };
}
async function getCrawlTaskState(): Promise<CrawlTaskState | null> {
const result = await chrome.storage.local.get(CRAWL_TASK_STORAGE_KEY);
const state = result[CRAWL_TASK_STORAGE_KEY];
return isCrawlTaskState(state) ? state : null;
}
async function setCrawlTaskState(state: CrawlTaskState): Promise<void> {
await chrome.storage.local.set({ [CRAWL_TASK_STORAGE_KEY]: state });
}
function createCrawlWindow(url: string): Promise<chrome.windows.Window> {
return new Promise((resolve, reject) => {
chrome.windows.create(
{
url,
type: 'normal',
focused: true,
width: 1280,
height: 900,
},
(windowInfo) => {
const runtimeError = chrome.runtime.lastError;
if (runtimeError) {
reject(new Error(runtimeError.message));
return;
}
if (!windowInfo?.id) {
reject(new Error('窗口创建失败'));
return;
}
resolve(windowInfo);
},
);
});
}
function removeWindow(windowId: number): Promise<void> {
return new Promise((resolve) => {
chrome.windows.remove(windowId, () => {
resolve();
});
});
}
function isCrawlTaskState(value: unknown): value is CrawlTaskState {
return typeof value === 'object' && value !== null && 'id' in value && 'steps' in value;
}