npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@skrillex1224/extension-toolkit

v1.0.1

Published

Shared runtime, logging, step execution, captcha monitoring, and Chrome extension helpers.

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_container

ApifyKit

名字保持和 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.createManagedTabscripting.executestorage.set/removeheaderRules.add/remove 都可能有副作用,默认重试可能创建重复 tab、重复执行页面脚本或覆盖状态。

Runtime.network.fetch 内置轻量 transient retry:

  • 默认重试 2 次,间隔 500ms。
  • GETHEADPUT 遇到 fetch-level 网络异常、timeout、HTTP 408/425/429/5xx 会自动重试。
  • POSTPATCH 只对 fetch-level 网络异常自动重试,不会因为 HTTP 5xx 默认重试,避免重复提交发布类请求。
  • 如果业务确认某个 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.DefaultGroupColorgrey)。该流程会串行化建组、复用缓存 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_000
  • CheckAuth: 30_000
  • GetStatus: 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 应作为独立业务包依赖本包。它负责:

  • publish
  • checkAuth
  • getStatus
  • 平台 adapter
  • 文章发布业务 code
  • 审核中、审核拒绝、发布成功、平台拒绝、未登录等业务状态

它不应重新实现 timeout、retry helper、Chrome wrappers、logger、runStep/runStepLoose 或 captcha monitor。