refactor(extension): class-based command registry + modular src layout
Restructure the MV3 background worker from a monolithic core.ts/index.ts
into a class-based command architecture. Behavior is identical — the 83
registered commands dispatch byte-for-byte the same as before.
Structure
- One class per command group, each extending CommandGroup and exporting a
`commands` map keyed by the full command id ("tabs.close"). Groups:
Navigation, TabsMutation, TabsQuery, Groups, Windows, Dom (dom/extract/
page), BrowserData (storage/cookies), Session (session/clients + autosave
+ lazy-tab activation), Perf (perf + jobs.status/cancel), Extension.
- CommandRegistry merges the group maps (throws on duplicate ids), routes
background specs to JobManager and paginates array results via
makePagedData. JobManager owns the job map + lifecycle. NativeConnection
owns the native-port lifecycle and the inbound message router.
- index.ts is now thin wiring: JobManager -> ctx -> assembleRegistry ->
onActivated -> NativeConnection.start().
- Infra classes live in classes/ (PascalCase, file = class name); command
groups in commands/; shared helpers split out of core.ts into core/
(errors, throttle, scripting, tab-helpers, group-helpers, storage); all
types moved into types/ (json, jobs, session, tabs, messages,
command-args) behind a barrel.
DRY cleanup
- resolveTabUrl(tabId) and assertScriptableUrl(url, action) collapse the
tab/URL-guard boilerplate duplicated across dom.ts and browser-data.ts.
- processInBatches() centralizes the throttled, cancellable batch loop
shared by tabs.close, group.close and tabs.merge_windows.
- captureCurrentSession() dedups the snapshot-and-signature block shared by
session.save and the autosave path.
- DomArgs type alias replaces 21 inline ContentArgs & { tabId? } copies.
- Drop fetchTabHtml's redundant retry loop (executeScript already retries
transient frame/tab errors), a dead tabInfo import, and two stale
comments referencing a removed asArgs helper.
Type safety & tests
- Full noImplicitAny; no `any`/`unknown` annotations remain in src.
- JS unit-test harness using node --test + node:assert (zero new deps),
bundled via the existing esbuild. Covers JobManager retention/lifecycle
and the autosave listener-wiring/debounce with an in-memory chrome mock.
- The structural pytest checks track the new file homes and the centralized
processInBatches helper.
Verification: npm run check:extension green (tsc + esbuild 84.5kb +
node --check + 18 JS tests); uv run pytest -q -> 409 passed, 105 skipped.
No version bump.
This commit is contained in:
@@ -1,34 +1,45 @@
|
||||
// @ts-nocheck
|
||||
import { getAliases } from '../core';
|
||||
export async function windowsList() {
|
||||
const windows = await chrome.windows.getAll({ populate: true });
|
||||
const aliases = await getAliases();
|
||||
return windows.map(w => ({
|
||||
id: w.id,
|
||||
alias: aliases[w.id] || null,
|
||||
focused: w.focused,
|
||||
state: w.state,
|
||||
tabCount: (w.tabs || []).length,
|
||||
}));
|
||||
}
|
||||
import { CommandGroup } from '../classes/CommandGroup';
|
||||
import type { CommandEntry } from '../classes/CommandGroup';
|
||||
import type { WindowsRenameArgs, WindowsCloseArgs, WindowsOpenArgs } from '../types';
|
||||
|
||||
export async function windowsRename({ windowId, name }) {
|
||||
const aliases = await getAliases();
|
||||
aliases[windowId] = name;
|
||||
await chrome.storage.local.set({ windowAliases: aliases });
|
||||
return { windowId, name };
|
||||
}
|
||||
export class WindowsCommands extends CommandGroup {
|
||||
readonly namespace = "windows";
|
||||
readonly commands: Record<string, CommandEntry> = {
|
||||
"windows.list": () => this.windowsList(),
|
||||
"windows.rename": (a: WindowsRenameArgs) => this.windowsRename(a),
|
||||
"windows.close": (a: WindowsCloseArgs) => this.windowsClose(a),
|
||||
"windows.open": (a: WindowsOpenArgs) => this.windowsOpen(a),
|
||||
};
|
||||
|
||||
export async function windowsClose({ windowId }) {
|
||||
await chrome.windows.remove(windowId);
|
||||
return { windowId };
|
||||
}
|
||||
private async windowsList() {
|
||||
const windows = await chrome.windows.getAll({ populate: true });
|
||||
const aliases = await getAliases();
|
||||
return windows.map(w => ({
|
||||
id: w.id,
|
||||
alias: aliases[w.id] || null,
|
||||
focused: w.focused,
|
||||
state: w.state,
|
||||
tabCount: (w.tabs || []).length,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function windowsOpen({ url }) {
|
||||
const createData = { focused: true };
|
||||
if (url) createData.url = url;
|
||||
const w = await chrome.windows.create(createData);
|
||||
return { id: w.id };
|
||||
}
|
||||
private async windowsRename({ windowId, name }: WindowsRenameArgs) {
|
||||
const aliases = await getAliases();
|
||||
aliases[windowId] = name;
|
||||
await chrome.storage.local.set({ windowAliases: aliases });
|
||||
return { windowId, name };
|
||||
}
|
||||
|
||||
// ── DOM / Extract ─────────────────────────────────────────────────────────────
|
||||
private async windowsClose({ windowId }: WindowsCloseArgs) {
|
||||
await chrome.windows.remove(windowId);
|
||||
return { windowId };
|
||||
}
|
||||
|
||||
private async windowsOpen({ url }: WindowsOpenArgs) {
|
||||
const createData: chrome.windows.CreateData = { focused: true };
|
||||
if (url) createData.url = url;
|
||||
const w = await chrome.windows.create(createData);
|
||||
return { id: w.id };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user