@skrillex1224/extension-toolkit
v1.0.1
Published
Shared runtime, logging, step execution, captcha monitoring, and Chrome extension helpers.
Maintainers
Readme
@skrillex1224/extension-toolkit
Chrome extension 公共基础设施包。
它只提供通用能力:日志、步骤执行、timeout、显式 retry、Chrome API 薄封装、验证码监测、结构化错误和少量工具函数。
它不包含文章发布业务,不包含平台 adapter,不定义审核中/发布成功/审核拒绝等业务状态,也不承载正式插件 UI。
能力边界
本包目前已经覆盖媒体发布 adapter 需要的公共 Chrome extension 基建:
- 步骤日志、timeout、显式 retry。
- Chrome network fetch。
- 受管 tab 创建、加载等待、关闭和 tab group。
scripting.execute,包括可选world: 'MAIN'。- declarativeNetRequest header rules。
- storage。
- 验证码/安全校验检测信号。
业务包不应把 timeout、retry、Chrome API wrapper、logger、runStep、header rules primitive 或 captcha monitor 重新实现一遍。
下列内容不属于当前 toolkit 责任:
- 平台 publish/checkAuth/getStatus 语义。
- 审核中、公开链接同步中、审核拒绝等业务状态。
- 平台 URL、平台 header rule 组合、平台 page helper。
- 业务级自动 retry 策略。
如果后续多个业务包都需要同一个 Chrome API 生命周期组合,先补 toolkit;如果只是某个 adapter 内部复用,留在业务包。
安装
npm install @skrillex1224/extension-toolkit公共入口
import { useExtensionToolkit } from '@skrillex1224/extension-toolkit';
const {
Constants,
Errors,
Logger,
ApifyKit,
Runtime,
Captcha,
Utils,
} = useExtensionToolkit();useExtensionToolkit() 不接受配置参数。测试时如果需要 fake Chrome API,直接设置 globalThis.chrome。
Codes
Constants.Code 只包含基础设施 code:
Constants.Code.Success; // 0
Constants.Code.Timeout; // 30000003
Constants.Code.ChromeUnavailable; // 30000005
Constants.Code.ScriptingFailed; // 30000011
Constants.Code.CaptchaDetected; // 30000015文章发布状态、审核状态、平台拒绝、未登录等业务 code 应由业务包定义。
日志
Logger.info('开始处理', { tabId: 123 });
Logger.warn('检测到异常状态', { url });
Logger.error('执行失败', error);
Logger.step('打开发布页', '开始', { platform: 'douyin' });
const logs = Logger.exportLogs();
Logger.clearLogs();日志会写到内存环形缓冲区,最多保留 500 条,同时输出到控制台:
[ExtensionToolkit] [Step] 开始 打开发布页
[ExtensionToolkit] [Chrome] tabs.createManagedTab 成功
[ExtensionToolkit] [Captcha] 检测到验证码 selector=#captcha_containerApifyKit
名字保持和 playwright-toolkit 风格一致,但这里不依赖 Apify SDK。
const Kit = ApifyKit.useApifyKit();
await Kit.runStep('打开发布页', async () => {
return Runtime.tabs.createManagedTab({
url: 'https://example.com',
timeoutMs: Constants.TimeoutMs.ManagedTab,
});
});runStep:
- 自动记录开始、成功、失败、重试、超时日志。
- 支持
timeoutMs。 - retry 默认关闭,只有显式传
retry.times > 0才启用。 - 失败会包装为
ExtensionToolkitError或其子类。
await Kit.runStep('请求平台接口', requestPlatform, {
timeoutMs: 60_000,
retry: {
times: 2,
delayMs: 1000,
},
data: { platform: 'douyin' },
});runStepLoose 同样记录日志、支持 timeout/retry,但失败时保留原始错误:
await Kit.runStepLoose('读取原始异常', async () => {
throw new Error('raw platform error');
});Runtime
Runtime 是 Chrome API 薄封装。所有方法都支持 timeoutMs。
除 Runtime.network.fetch 外,retry 只在显式传入时启用。原因是 tabs.createManagedTab、scripting.execute、storage.set/remove、headerRules.add/remove 都可能有副作用,默认重试可能创建重复 tab、重复执行页面脚本或覆盖状态。
Runtime.network.fetch 内置轻量 transient retry:
- 默认重试 2 次,间隔 500ms。
GET、HEAD、PUT遇到 fetch-level 网络异常、timeout、HTTP408/425/429/5xx会自动重试。POST、PATCH只对 fetch-level 网络异常自动重试,不会因为 HTTP5xx默认重试,避免重复提交发布类请求。- 如果业务确认某个
POST是幂等的,可以显式传retry覆盖默认策略。
const tab = await Runtime.tabs.createManagedTab({
url: 'https://example.com',
});
await Runtime.tabs.waitLoaded(tab.id, { timeoutMs: 60_000 });
const title = await Runtime.scripting.execute(
tab.id,
() => document.title,
[],
{ timeoutMs: 10_000 },
);
await Runtime.storage.set({ lastTabId: tab.id });
const state = await Runtime.storage.get(['lastTabId']);
await Runtime.tabs.close(tab.id);
const response = await Runtime.network.fetch('https://upload.example.com/file', {
method: 'PUT',
body: fileBlob,
timeoutMs: 60_000,
});Runtime.tabs.createManagedTab() 会把 tab 放进同一窗口同标题的受管 tabGroup。默认分组标题是 Constants.ManagedTabs.DefaultGroupTitle(📑),默认颜色是 Constants.ManagedTabs.DefaultGroupColor(grey)。该流程会串行化建组、复用缓存 groupId、合并同标题重复 group,并默认把 group 设置为展开状态;需要收起时可传 collapsed: true。
Runtime.tabs.createManagedTab() 不支持 active 参数,底层固定以 active: false 打开 tab,避免发布流程抢前台焦点。
打开受管 tab 时会先在同标题 tabGroup 内按 URL 查找,匹配时会忽略 query 和 hash;如果已经存在,则复用这个 tab,不再创建新 tab。新建后会按 maxTabsPerGroup 控制 group 内 tab 数量,默认 Constants.ManagedTabs.MaxTabsPerGroup 为 10,超过时关闭最早的非当前 tab。需要自动选色时可以显式传 groupColor: Constants.ManagedTabs.AutoGroupColor。
业务操作默认超时统一放在 Constants.OperationTimeoutMs:
Publish:90_000CheckAuth:30_000GetStatus:30_000
可用模块:
Runtime.tabs.createManagedTab(options)Runtime.tabs.close(tabId, options?)Runtime.tabs.waitLoaded(tabId, options?)Runtime.tabGroups.ensure(options)Runtime.scripting.execute(tabId, func, args?, options?)Runtime.storage.get(keys, options?)Runtime.storage.set(values, options?)Runtime.storage.remove(keys, options?)Runtime.network.fetch(input, init?, options?)
Captcha Monitor
验证码监测只负责检测和通知,不自动打码,不内置第三方 token。
const monitor = Captcha.useCaptchaMonitor({
tabId: tab.id,
selectors: ['#captcha_container', 'iframe[src*="verifycenter"]'],
urlPatterns: ['verifycenter', 'captcha'],
intervalMs: Constants.TimeoutMs.CaptchaMonitorPoll,
onDetected: async (event) => {
Logger.warn('captcha detected', event);
},
});
await monitor.stop();事件结构:
{
code: Constants.Code.CaptchaDetected,
tabId: 123,
reason: 'selector',
selector: '#captcha_container',
url: 'https://example.com/editor',
detectedAt: 1784110682682,
}业务包可以把这个信号映射为自己的 publish/checkAuth/getStatus code。toolkit 不替业务层决定最终状态。
测试 fake Chrome
单元测试中可以直接挂 globalThis.chrome:
globalThis.chrome = {
runtime: {},
tabs: {
create(options, callback) {
callback({ id: 123, ...options });
},
},
};测试结束后删除:
delete globalThis.chrome;下一步边界
chrome-article-publish-extension 应作为独立业务包依赖本包。它负责:
publishcheckAuthgetStatus- 平台 adapter
- 文章发布业务 code
- 审核中、审核拒绝、发布成功、平台拒绝、未登录等业务状态
它不应重新实现 timeout、retry helper、Chrome wrappers、logger、runStep/runStepLoose 或 captcha monitor。
