Files

256 lines
7.5 KiB
TypeScript
Raw Permalink Normal View History

2026-04-12 09:56:18 +08:00
import type { MonitoringCycleKickoffResponse } from "@geo/shared-types";
import { browser } from "wxt/browser";
import { defineBackground } from "wxt/utils/define-background";
2026-04-12 09:56:18 +08:00
import {
patchDoubaoMonitoringRuntimeState,
readDoubaoMonitoringRuntimePatchFromRequestUrl,
} from "../src/monitoring-adapters/doubao-state";
import { handleMonitoringAction, runMonitoringCycle, type MonitoringCycleMode } from "../src/monitoring-runtime";
import { handlePublisherAction } from "../src/runtime";
2026-04-12 09:56:18 +08:00
import { getExtensionState, type ExtensionStorageState } from "../src/storage";
type BackgroundSuccessResponse = {
ok: true;
2026-04-12 09:56:18 +08:00
data: unknown;
};
type BackgroundErrorResponse = {
ok: false;
error: string;
};
2026-04-12 09:56:18 +08:00
const PUBLISHER_HEARTBEAT_ALARM = "geo.publisher.background-heartbeat";
const MONITORING_HEARTBEAT_ALARM = "geo.monitoring.background-heartbeat";
const MONITORING_QUEUE_ALARM = "geo.monitoring.queue-heartbeat";
const MONITORING_WARMUP_INTERVAL_MS = 10 * 60 * 1000;
const MONITORING_HEARTBEAT_INTERVAL_MINUTES = 15;
const MONITORING_HEARTBEAT_START_JITTER_MS = 3 * 60 * 1000;
const DOUBAO_COMPLETION_REQUEST_FILTER = {
urls: ["https://www.doubao.com/chat/completion*", "https://doubao.com/chat/completion*"],
};
let monitoringCycleInFlight: Promise<unknown> | null = null;
function randomMonitoringHeartbeatStartDelayMs(): number {
return Math.floor(Math.random() * (MONITORING_HEARTBEAT_START_JITTER_MS + 1));
}
async function ensureBackgroundAlarms(): Promise<void> {
browser.alarms.create(PUBLISHER_HEARTBEAT_ALARM, {
periodInMinutes: 60,
});
browser.alarms.create(MONITORING_HEARTBEAT_ALARM, {
when: Date.now() + randomMonitoringHeartbeatStartDelayMs(),
periodInMinutes: MONITORING_HEARTBEAT_INTERVAL_MINUTES,
});
}
function shouldWarmupMonitoring(state: ExtensionStorageState): boolean {
if (!state.plugin_installation_id || !state.installation_token || !state.api_base_url) {
return false;
}
if (!state.monitoring_last_heartbeat_at) {
return true;
}
const lastHeartbeatAt = new Date(state.monitoring_last_heartbeat_at).getTime();
if (Number.isNaN(lastHeartbeatAt)) {
return true;
}
return Date.now() - lastHeartbeatAt >= MONITORING_WARMUP_INTERVAL_MS;
}
function registerMonitoringRuntimeCapture(): void {
if (!browser.webRequest?.onBeforeRequest) {
return;
}
browser.webRequest.onBeforeRequest.addListener(
(details) => {
const patch = readDoubaoMonitoringRuntimePatchFromRequestUrl(details.url);
if (!patch) {
return undefined;
}
void patchDoubaoMonitoringRuntimeState(patch).catch((error) => {
console.warn("[geo-monitoring] failed to persist doubao runtime patch", error);
});
return undefined;
},
DOUBAO_COMPLETION_REQUEST_FILTER,
);
}
async function warmupMonitoringRuntime(): Promise<void> {
const state = await getExtensionState();
if (!shouldWarmupMonitoring(state)) {
return;
}
await runMonitoringCycleSingleFlight("automatic");
}
async function syncMonitoringQueueAlarm(): Promise<void> {
const state = await getExtensionState();
const nextExecuteAt = state.monitoring_task_queue.reduce<number | null>((earliest, task) => {
const timestamp = new Date(task.next_execute_at).getTime();
if (Number.isNaN(timestamp)) {
return earliest;
}
if (earliest == null) {
return timestamp;
}
return Math.min(earliest, timestamp);
}, null);
await browser.alarms.clear(MONITORING_QUEUE_ALARM);
if (nextExecuteAt == null) {
return;
}
browser.alarms.create(MONITORING_QUEUE_ALARM, {
when: Math.max(Date.now() + 1000, nextExecuteAt),
});
}
function runMonitoringCycleSingleFlight(mode: MonitoringCycleMode): Promise<unknown> {
if (monitoringCycleInFlight) {
return monitoringCycleInFlight;
}
monitoringCycleInFlight = runMonitoringCycle(mode).finally(() => {
monitoringCycleInFlight = null;
void syncMonitoringQueueAlarm().catch((error) => {
console.warn("[geo-monitoring] failed to sync queue alarm", error);
});
});
return monitoringCycleInFlight;
}
function kickoffMonitoringCycle(): MonitoringCycleKickoffResponse {
if (monitoringCycleInFlight) {
return {
accepted: true,
status: "already_running",
message: "monitoring cycle is already running in background",
};
}
void runMonitoringCycleSingleFlight("manual").catch((error) => {
console.warn("[geo-monitoring] background cycle failed", error);
});
return {
accepted: true,
status: "started",
message: "monitoring cycle started in background",
};
}
export default defineBackground(() => {
void getExtensionState();
2026-04-12 09:56:18 +08:00
void ensureBackgroundAlarms();
registerMonitoringRuntimeCapture();
void warmupMonitoringRuntime().catch((error) => {
console.warn("[geo-monitoring] warmup cycle failed", error);
});
void syncMonitoringQueueAlarm();
browser.runtime.onInstalled.addListener(() => {
void getExtensionState();
2026-04-12 09:56:18 +08:00
void ensureBackgroundAlarms();
void warmupMonitoringRuntime().catch((error) => {
console.warn("[geo-monitoring] install warmup cycle failed", error);
});
void syncMonitoringQueueAlarm();
});
browser.runtime.onStartup.addListener(() => {
void getExtensionState();
void ensureBackgroundAlarms();
void warmupMonitoringRuntime().catch((error) => {
console.warn("[geo-monitoring] startup warmup cycle failed", error);
});
2026-04-12 09:56:18 +08:00
void syncMonitoringQueueAlarm();
});
browser.alarms.onAlarm.addListener(async (alarm) => {
2026-04-12 09:56:18 +08:00
if (alarm.name === PUBLISHER_HEARTBEAT_ALARM) {
await getExtensionState();
return;
}
if (alarm.name === MONITORING_HEARTBEAT_ALARM) {
try {
await runMonitoringCycleSingleFlight("automatic");
} catch (error) {
console.warn("[geo-monitoring] cycle failed", error);
}
return;
}
2026-04-12 09:56:18 +08:00
if (alarm.name === MONITORING_QUEUE_ALARM) {
try {
await runMonitoringCycleSingleFlight("automatic");
} catch (error) {
console.warn("[geo-monitoring] queue cycle failed", error);
}
}
});
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
2026-04-12 09:56:18 +08:00
if (!message) {
return undefined;
}
2026-04-12 09:56:18 +08:00
if (message.type === "geo.publisher.request") {
void handlePublisherAction(message.action, message.payload)
.then((data) => {
sendResponse({
ok: true,
data,
} satisfies BackgroundSuccessResponse);
})
.catch((error: unknown) => {
sendResponse({
ok: false,
error: error instanceof Error ? error.message : "publisher_plugin_unknown_error",
} satisfies BackgroundErrorResponse);
});
return true;
}
if (message.type === "geo.monitoring.request") {
if (message.action === "kickoffCycle") {
sendResponse({
ok: true,
2026-04-12 09:56:18 +08:00
data: kickoffMonitoringCycle(),
} satisfies BackgroundSuccessResponse);
2026-04-12 09:56:18 +08:00
return true;
}
2026-04-12 09:56:18 +08:00
const action = message.action === "runCycle" ? runMonitoringCycleSingleFlight("manual") : handleMonitoringAction(message.action);
void action
.then((data) => {
sendResponse({
ok: true,
data,
} satisfies BackgroundSuccessResponse);
})
.catch((error: unknown) => {
sendResponse({
ok: false,
error: error instanceof Error ? error.message : "monitoring_plugin_unknown_error",
} satisfies BackgroundErrorResponse);
});
return true;
}
return undefined;
});
});