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

@channek/sandbox-sdk

v0.1.2

Published

Channek T1 沙箱插件的零依赖 MessagePort SDK

Readme

@channek/sandbox-sdk

Channek T1 插件的零依赖 ESM SDK。插件入口在 sandbox="allow-scripts" 的 opaque-origin iframe 内运行;所有宿主能力都经一次性移交的 MessagePort 白名单协议调用。

npm i @channek/sandbox-sdk

它是构建期依赖,必须打进你的 bundle。 沙箱文档的 CSP 是 script-src plugin://<你的插件 id>, 不含任何外部源——从 CDN 引一个 <script> 会被当场挡掉,装进 node_modules 再由 esbuild / Vite 打成插件目录内的单文件才跑得起来。

import { createSandboxSdk } from '@channek/sandbox-sdk';

const sdk = createSandboxSdk();
sdk.onInit(async init => {
  document.body.textContent = await sdk.readFile();
  sdk.setState({ scrollY: 0 });
});

不要假设 event.origin 是插件 URL——沙箱 origin 固定为 opaque。视图状态(滚动位置、展开态) 用 setState 交还宿主。

Web Storage

opaque origin 下 localStorage / sessionStorage 读一下就抛 SecurityError,靠它们做持久化的 现成库会当场死。installWebStorage() 用「内存镜像 + 经桥落盘」把标准同步语义补回来:

const sdk = createSandboxSdk();
sdk.onInit(async () => {
  await sdk.installWebStorage();   // await 之后再启动业务代码:同步读的前提是镜像已拉完
  mountApp();
});

之后 localStorage.getItem() / localStorage.foo = 'x' 等标准写法(含裸标识符)都可用。 差异:容量约 448KiB(超限抛 QuotaExceededError 并回滚本次写)、落盘合并且最多滞后数秒、 进程崩溃可能丢最后一批、不触发 storage 事件、作用域按插件而非 origin。 传 { global: false } 可只取返回的 { local, session } 不动全局; { scope: 'workspace' } 把 localStorage 切成每频道一份。

IndexedDB 依然没有。要更强的一致性用底层 sdk.storage.get/set(异步、直达 plugin-storage), 更大的量走「T2 侧持有 + rpc 分页取」。

功能区(suite section)最小用例

第三方功能区 = manifest 的 ui.suiteSection 贡献 + 沙箱入口 html(多区插件各区用 decl.entry 指各自入口——入口即身份,init 里没有 sectionId)。数据、导航、剪贴板全部走 suite 桥(宿主 typed 白名单 op);素材图片 / 视频不过桥,用应答里的 channek-media: capability URL 直接 <img>/<video> 加载。

import {
  createSandboxSdk,
  suiteContextFromInit,
  SuiteBridgeError,
} from '@channek/sandbox-sdk';

const sdk = createSandboxSdk();

sdk.onInit(async init => {
  const context = suiteContextFromInit(init);
  if (!context) return; // 非 suite 承载面(viewer/panel):suite 桥会被宿主以 not-a-suite 拒
  if (context.workspaceId === null) {
    document.body.textContent = '当前没有打开的频道';
    return;
  }
  try {
    // ≡ 渲染 IPC workspace:assetsOverview 的 DTO(精确形状见下「类型怎么拿」)
    const overview = await sdk.suite.assetsOverview();
    render(overview);
  } catch (error) {
    if (error instanceof SuiteBridgeError && error.code === 'response-too-large') {
      document.body.textContent = '素材库过大,等待分页支持';
      return;
    }
    throw error;
  }
});

// 导航(宿主逐条校验实参,防导航钓鱼):
await sdk.suite.openContent('item-1');
await sdk.suite.openSection('homes');

// 剪贴板(沙箱 iframe 拿不到 navigator.clipboard,必须过桥):
await sdk.suite.clipboardWriteText('复制的文案');

要点(第三方作者常踩):

  • 类型怎么拿:读 op 应答在线上按 unknown 承载;精确 DTO (WorkspaceAssetsOverviewDTO / WorkspaceOperationsSnapshotDTO)从 @channek/ipc-contract 类型层 import(import type),运行时保持零依赖。
  • 错误分支认 SuiteBridgeError.code(unknown-op / not-a-suite / response-too-large / permission-denied / consent-required / timeout / invalid-payload / rate-limited / host-error),别 parse message 文本——文案不冻结, 码才冻结。
  • 写 op(sdk.suite.saveBacklog / sdk.suite.updateField):manifest 要声明 workspace:write(缺 → permission-denied);每 (插件, 频道) 首次写会得 consent-required,同时宿主弹一次同意确认——把它渲染成「等待用户允许后重试」,别当致命 错误。revision 冲突以人类可读错误回来(host-error),刷新快照(operationsSnapshot) 取新 revision 再重试。
  • 剪贴板本地先限流:文本 ≤32KiB、每分钟 ≤10 次(SUITE_CLIPBOARD_LIMITS),超限 SDK 直接拒、不出桥——宿主那侧的限额是审计面,不是你的节流器。
  • 大清单自己虚拟滚动:读应答上限 1MiB(SUITE_RESPONSE_LIMITS),超限宿主拒并提示 分页;iframe 内的渲染性能是插件职责。