1
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { handleBackgroundCommand, handleInstalled, handleStartup, handleWindowRemoved } from './service';
|
||||
import { broadcastCrawlStorageChange, handleExternalConnect, handleExternalMessage } from './service/externalBridge';
|
||||
import type { BackgroundCommand } from './types';
|
||||
|
||||
chrome.runtime.onInstalled.addListener(() => {
|
||||
@@ -19,16 +20,22 @@ chrome.windows.onRemoved.addListener((windowId) => {
|
||||
});
|
||||
|
||||
chrome.runtime.onMessageExternal.addListener((message, _sender, sendResponse) => {
|
||||
if (message.type === 'STORE_AI_PING') {
|
||||
void handleExternalMessage(message).then(sendResponse).catch((error: unknown) => {
|
||||
sendResponse({
|
||||
success: true,
|
||||
version: chrome.runtime.getManifest().version,
|
||||
ok: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
chrome.runtime.onConnectExternal.addListener(handleExternalConnect);
|
||||
|
||||
chrome.storage.onChanged.addListener((changes, areaName) => {
|
||||
broadcastCrawlStorageChange(changes, areaName);
|
||||
});
|
||||
|
||||
/**
|
||||
* Wrap background command handling so async errors can still be returned to callers.
|
||||
*/
|
||||
|
||||
@@ -18,6 +18,11 @@ const activeCrawlControllers = new Map<string, AbortController>();
|
||||
*/
|
||||
export async function startCrawl(platformId: string): Promise<CrawlStateResponse> {
|
||||
const platform = getPlatformById(platformId);
|
||||
const currentState = await getCrawlTaskState();
|
||||
|
||||
if (currentState && ['running', 'paused'].includes(currentState.status)) {
|
||||
return { ok: true, data: currentState };
|
||||
}
|
||||
|
||||
if (!platform) {
|
||||
return { ok: false, error: '平台配置不存在' };
|
||||
|
||||
207
src/background/service/externalBridge.ts
Normal file
207
src/background/service/externalBridge.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
import { platformConfigs } from '@/config/platforms';
|
||||
import type { CrawlTaskState } from '@/types';
|
||||
import { cancelCrawl, startCrawl } from './crawlTask';
|
||||
import { getCrawlTaskState } from './taskState';
|
||||
|
||||
const CRAWL_TASK_STORAGE_KEY = 'crawlTaskState';
|
||||
const EXTERNAL_PORT_NAME = 'DIANSHAN_CRAWL';
|
||||
|
||||
type ExternalAction =
|
||||
| 'DIANSHAN_PING'
|
||||
| 'DIANSHAN_START_CRAWL'
|
||||
| 'DIANSHAN_GET_CRAWL_STATE'
|
||||
| 'DIANSHAN_CANCEL_CRAWL'
|
||||
| 'STORE_AI_PING';
|
||||
|
||||
interface ExternalMessage {
|
||||
type?: ExternalAction;
|
||||
action?: ExternalAction;
|
||||
payload?: {
|
||||
platformId?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ExternalResponse<T = unknown> {
|
||||
ok: boolean;
|
||||
success?: boolean;
|
||||
type?: string;
|
||||
data?: T;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
interface CrawlWebPayload {
|
||||
state: CrawlTaskState | null;
|
||||
result: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
const externalPorts = new Set<chrome.runtime.Port>();
|
||||
|
||||
export async function handleExternalMessage(message: ExternalMessage): Promise<ExternalResponse> {
|
||||
const action = message.type ?? message.action;
|
||||
|
||||
switch (action) {
|
||||
case 'STORE_AI_PING':
|
||||
case 'DIANSHAN_PING':
|
||||
return {
|
||||
ok: true,
|
||||
success: true,
|
||||
data: {
|
||||
version: chrome.runtime.getManifest().version,
|
||||
platforms: platformConfigs.map((platform) => ({
|
||||
id: platform.id,
|
||||
name: platform.name,
|
||||
})),
|
||||
},
|
||||
};
|
||||
case 'DIANSHAN_START_CRAWL':
|
||||
return startCrawlForWebsite(message.payload?.platformId);
|
||||
case 'DIANSHAN_GET_CRAWL_STATE':
|
||||
return {
|
||||
ok: true,
|
||||
data: buildCrawlWebPayload(await getCrawlTaskState()),
|
||||
};
|
||||
case 'DIANSHAN_CANCEL_CRAWL':
|
||||
await cancelCrawl();
|
||||
return {
|
||||
ok: true,
|
||||
data: buildCrawlWebPayload(null),
|
||||
};
|
||||
default:
|
||||
return { ok: false, error: 'unknown_external_action' };
|
||||
}
|
||||
}
|
||||
|
||||
export function handleExternalConnect(port: chrome.runtime.Port): void {
|
||||
if (port.name !== EXTERNAL_PORT_NAME) {
|
||||
port.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
externalPorts.add(port);
|
||||
|
||||
getCrawlTaskState()
|
||||
.then((state) => {
|
||||
postToExternalPort(port, {
|
||||
ok: true,
|
||||
type: 'DIANSHAN_CRAWL_STATE',
|
||||
data: buildCrawlWebPayload(state),
|
||||
});
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
postToExternalPort(port, {
|
||||
ok: false,
|
||||
type: 'DIANSHAN_CRAWL_ERROR',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
});
|
||||
|
||||
port.onMessage.addListener((message: ExternalMessage) => {
|
||||
void handleExternalMessage(message)
|
||||
.then((response) => {
|
||||
postToExternalPort(port, response);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
postToExternalPort(port, {
|
||||
ok: false,
|
||||
type: 'DIANSHAN_CRAWL_ERROR',
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
port.onDisconnect.addListener(() => {
|
||||
externalPorts.delete(port);
|
||||
});
|
||||
}
|
||||
|
||||
export function broadcastCrawlStorageChange(changes: Record<string, chrome.storage.StorageChange>, areaName: string): void {
|
||||
if (areaName !== 'local') {
|
||||
return;
|
||||
}
|
||||
|
||||
const change = changes[CRAWL_TASK_STORAGE_KEY];
|
||||
|
||||
if (!change) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextState = isCrawlTaskState(change.newValue) ? change.newValue : null;
|
||||
const oldState = isCrawlTaskState(change.oldValue) ? change.oldValue : null;
|
||||
const type = getBroadcastType(nextState, oldState);
|
||||
|
||||
broadcastToExternalPorts({
|
||||
ok: true,
|
||||
type,
|
||||
data: buildCrawlWebPayload(nextState),
|
||||
});
|
||||
}
|
||||
|
||||
async function startCrawlForWebsite(platformId?: string): Promise<ExternalResponse<CrawlWebPayload>> {
|
||||
const response = await startCrawl(platformId ?? platformConfigs[0]?.id ?? '');
|
||||
|
||||
return {
|
||||
ok: response.ok,
|
||||
type: 'DIANSHAN_CRAWL_STARTED',
|
||||
data: buildCrawlWebPayload(response.data ?? null),
|
||||
error: response.error,
|
||||
};
|
||||
}
|
||||
|
||||
function buildCrawlWebPayload(state: CrawlTaskState | null): CrawlWebPayload {
|
||||
return {
|
||||
state,
|
||||
result: state?.status === 'completed' ? collectStepResults(state) : null,
|
||||
};
|
||||
}
|
||||
|
||||
function collectStepResults(state: CrawlTaskState): Record<string, unknown> {
|
||||
return Object.fromEntries(
|
||||
state.steps.map((step) => [
|
||||
step.uniqueKey,
|
||||
{
|
||||
name: step.name,
|
||||
status: step.status,
|
||||
result: step.result ?? null,
|
||||
message: step.message ?? null,
|
||||
},
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
function getBroadcastType(nextState: CrawlTaskState | null, oldState: CrawlTaskState | null): string {
|
||||
if (!nextState) {
|
||||
return oldState ? 'DIANSHAN_CRAWL_CLEARED' : 'DIANSHAN_CRAWL_STATE';
|
||||
}
|
||||
|
||||
if (nextState.status === 'completed') {
|
||||
return 'DIANSHAN_CRAWL_DONE';
|
||||
}
|
||||
|
||||
if (nextState.status === 'failed') {
|
||||
return 'DIANSHAN_CRAWL_FAILED';
|
||||
}
|
||||
|
||||
if (nextState.status === 'canceled') {
|
||||
return 'DIANSHAN_CRAWL_CANCELED';
|
||||
}
|
||||
|
||||
return 'DIANSHAN_CRAWL_STATE';
|
||||
}
|
||||
|
||||
function broadcastToExternalPorts(message: ExternalResponse<CrawlWebPayload>): void {
|
||||
for (const port of externalPorts) {
|
||||
postToExternalPort(port, message);
|
||||
}
|
||||
}
|
||||
|
||||
function postToExternalPort(port: chrome.runtime.Port, message: ExternalResponse): void {
|
||||
try {
|
||||
port.postMessage(message);
|
||||
} catch {
|
||||
externalPorts.delete(port);
|
||||
}
|
||||
}
|
||||
|
||||
function isCrawlTaskState(value: unknown): value is CrawlTaskState {
|
||||
return typeof value === 'object' && value !== null && 'id' in value && 'steps' in value;
|
||||
}
|
||||
Reference in New Issue
Block a user