{"_id":"@channek/sandbox-sdk","_rev":"5-aeb09d953eeeda46766db9906866d95b","name":"@channek/sandbox-sdk","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@channek/sandbox-sdk","version":"0.1.0","keywords":["channek","plugin","sdk","sandbox","messageport"],"author":{"name":"Links","email":"archeruuu@gmail.com"},"license":"Apache-2.0","_id":"@channek/sandbox-sdk@0.1.0","maintainers":[{"name":"channek","email":"h96599021@gmail.com"}],"homepage":"https://github.com/garveyhu/Channek","bugs":{"url":"https://github.com/garveyhu/Channek/issues"},"dist":{"shasum":"9a95538abe0f01ccc3191efb6ecf888d7ac4ec31","tarball":"https://registry.npmjs.org/@channek/sandbox-sdk/-/sandbox-sdk-0.1.0.tgz","fileCount":25,"integrity":"sha512-xjd43WqcWsHSnZRJq4ueLWrG9nbD1KvR7JGHIlpmYUVGY68drQz8E/DVLs0WK6Kl/caYtCWXNd/RRdHctoLdsw==","signatures":[{"sig":"MEUCIQC1NbAZt0MqWdsMkggm5YQqSoN34Jicha1OBN5tfk8tdQIgXjTnXZOB48sZPlFupUq2MfD5ZurIotlCODLmLPhZU2s=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":207200},"main":"./src/index.ts","type":"module","types":"./src/index.ts","exports":{".":"./src/index.ts"},"gitHead":"d4d033c1a857a7beb54c931903a2126ff70cfe45","scripts":{"lint":"eslint src --max-warnings=0","test":"vitest run --exclude '**/dist/**'","build":"tsc -b","typecheck":"tsc -b --pretty false","prepublishOnly":"rm -rf dist && tsc -b --force"},"_npmUser":{"name":"channek","email":"h96599021@gmail.com"},"deprecated":"入口指向未打包的源码路径（main 为 ./src/index.ts 而包内无 src/），import 会解析失败。请用 >=0.1.1。","repository":{"url":"git+https://github.com/garveyhu/Channek.git","type":"git","directory":"shared/sandbox-sdk"},"_npmVersion":"10.9.2","description":"Channek T1 沙箱插件的零依赖 MessagePort SDK","directories":{},"sideEffects":false,"_nodeVersion":"23.11.0","publishConfig":{"main":"./dist/index.js","types":"./dist/index.d.ts","access":"public","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}}},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^27.4.0","vitest":"^4.0.18","typescript":"^5.7.0","@types/node":"^22.10.0"},"_npmOperationalInternal":{"tmp":"tmp/sandbox-sdk_0.1.0_1786264707525_0.870480239342851","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@channek/sandbox-sdk","version":"0.1.1","keywords":["channek","plugin","sdk","sandbox","messageport"],"author":{"name":"Links","email":"archeruuu@gmail.com"},"license":"Apache-2.0","_id":"@channek/sandbox-sdk@0.1.1","maintainers":[{"name":"channek","email":"h96599021@gmail.com"}],"homepage":"https://github.com/garveyhu/Channek","bugs":{"url":"https://github.com/garveyhu/Channek/issues"},"dist":{"shasum":"6e8efaa562a357482427609b075871e6d06fe7ab","tarball":"https://registry.npmjs.org/@channek/sandbox-sdk/-/sandbox-sdk-0.1.1.tgz","fileCount":25,"integrity":"sha512-9DBnC2WBj7agScw54nOyZKLV6063xOioZhCICu9axGtiQjDBMgPkLgsvOQGWzPd1PnaB6iKmC+JwSVGOVkVEFg==","signatures":[{"sig":"MEUCIBUyI8HM0mXiSEwb2Et30QpOzZbl6hnxLKh9r1vybvVNAiEAzsSSV1sXv0W3rbgUmWRfwHMzvqhwuqrKP6nAvA94QP8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":207419},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"516ad4d74be98ce9708da53892a683adb7490457","scripts":{"lint":"eslint src --max-warnings=0","test":"vitest run --exclude '**/dist/**'","build":"tsc -b","typecheck":"tsc -b --pretty false","prepublishOnly":"rm -rf dist && tsc -b --force"},"_npmUser":{"name":"channek","email":"h96599021@gmail.com"},"repository":{"url":"git+https://github.com/garveyhu/Channek.git","type":"git","directory":"shared/sandbox-sdk"},"_npmVersion":"10.9.2","description":"Channek T1 沙箱插件的零依赖 MessagePort SDK","directories":{},"sideEffects":false,"_nodeVersion":"23.11.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^27.4.0","vitest":"^4.0.18","typescript":"^5.7.0","@types/node":"^22.10.0"},"_npmOperationalInternal":{"tmp":"tmp/sandbox-sdk_0.1.1_1786266168465_0.5004060346643544","host":"s3://npm-registry-packages-npm-production"},"deprecated":"产物里的相对导入缺 .js 扩展名，Node ESM 解析失败（ERR_MODULE_NOT_FOUND）。请用 >=0.1.2。"},"0.1.2":{"name":"@channek/sandbox-sdk","version":"0.1.2","keywords":["channek","plugin","sdk","sandbox","messageport"],"author":{"name":"Links","email":"archeruuu@gmail.com"},"license":"Apache-2.0","_id":"@channek/sandbox-sdk@0.1.2","maintainers":[{"name":"channek","email":"h96599021@gmail.com"}],"homepage":"https://github.com/garveyhu/Channek","bugs":{"url":"https://github.com/garveyhu/Channek/issues"},"dist":{"shasum":"afa475e926c508b257e65ed39a56a401a95af23e","tarball":"https://registry.npmjs.org/@channek/sandbox-sdk/-/sandbox-sdk-0.1.2.tgz","fileCount":25,"integrity":"sha512-yVpky3E+sxtaz/OcIOMUsdujcAqK1tplkJQvY0guiCSyyubPjH7e7EEIDmDMKEe1jHYDZsy4Jg2A9dJJOsDGTQ==","signatures":[{"sig":"MEUCIQD2kmhF+tq0pdCzMdLPSrdvp2ID/+Gmbpn04zKNgtUYHQIge1AISq16sStQnHH1cBZIxfsxAmvFAgt6vuYnq43WJFQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":207492},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"gitHead":"516ad4d74be98ce9708da53892a683adb7490457","scripts":{"lint":"eslint src --max-warnings=0","test":"vitest run --exclude '**/dist/**'","build":"tsc -b","typecheck":"tsc -b --pretty false","prepublishOnly":"rm -rf dist && tsc -b --force"},"_npmUser":{"name":"channek","email":"h96599021@gmail.com"},"repository":{"url":"git+https://github.com/garveyhu/Channek.git","type":"git","directory":"shared/sandbox-sdk"},"_npmVersion":"10.9.2","description":"Channek T1 沙箱插件的零依赖 MessagePort SDK","directories":{},"sideEffects":false,"_nodeVersion":"23.11.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^27.4.0","vitest":"^4.0.18","typescript":"^5.7.0","@types/node":"^22.10.0"},"_npmOperationalInternal":{"tmp":"tmp/sandbox-sdk_0.1.2_1786266333337_0.8013600692505054","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-08-09T08:38:27.351Z","modified":"2026-08-09T09:05:51.501Z","0.1.0":"2026-08-09T08:38:27.685Z","0.1.1":"2026-08-09T09:02:48.607Z","0.1.2":"2026-08-09T09:05:33.572Z"},"bugs":{"url":"https://github.com/garveyhu/Channek/issues"},"author":{"name":"Links","email":"archeruuu@gmail.com"},"license":"Apache-2.0","homepage":"https://github.com/garveyhu/Channek","keywords":["channek","plugin","sdk","sandbox","messageport"],"repository":{"url":"git+https://github.com/garveyhu/Channek.git","type":"git","directory":"shared/sandbox-sdk"},"description":"Channek T1 沙箱插件的零依赖 MessagePort SDK","maintainers":[{"name":"channek","email":"h96599021@gmail.com"}],"readme":"# @channek/sandbox-sdk\n\nChannek T1 插件的零依赖 ESM SDK。插件入口在 `sandbox=\"allow-scripts\"` 的 opaque-origin\niframe 内运行；所有宿主能力都经一次性移交的 `MessagePort` 白名单协议调用。\n\n```bash\nnpm i @channek/sandbox-sdk\n```\n\n**它是构建期依赖，必须打进你的 bundle。** 沙箱文档的 CSP 是 `script-src plugin://<你的插件 id>`，\n不含任何外部源——从 CDN 引一个 `<script>` 会被当场挡掉，装进 `node_modules` 再由 esbuild /\nVite 打成插件目录内的单文件才跑得起来。\n\n```ts\nimport { createSandboxSdk } from '@channek/sandbox-sdk';\n\nconst sdk = createSandboxSdk();\nsdk.onInit(async init => {\n  document.body.textContent = await sdk.readFile();\n  sdk.setState({ scrollY: 0 });\n});\n```\n\n不要假设 `event.origin` 是插件 URL——沙箱 origin 固定为 opaque。视图状态（滚动位置、展开态）\n用 `setState` 交还宿主。\n\n## Web Storage\n\nopaque origin 下 `localStorage` / `sessionStorage` 读一下就抛 `SecurityError`，靠它们做持久化的\n现成库会当场死。`installWebStorage()` 用「内存镜像 + 经桥落盘」把标准同步语义补回来：\n\n```ts\nconst sdk = createSandboxSdk();\nsdk.onInit(async () => {\n  await sdk.installWebStorage();   // await 之后再启动业务代码：同步读的前提是镜像已拉完\n  mountApp();\n});\n```\n\n之后 `localStorage.getItem()` / `localStorage.foo = 'x'` 等标准写法（含裸标识符）都可用。\n差异：容量约 448KiB（超限抛 `QuotaExceededError` 并回滚本次写）、落盘合并且最多滞后数秒、\n进程崩溃可能丢最后一批、不触发 `storage` 事件、作用域按插件而非 origin。\n传 `{ global: false }` 可只取返回的 `{ local, session }` 不动全局；\n`{ scope: 'workspace' }` 把 `localStorage` 切成每频道一份。\n\n**IndexedDB 依然没有**。要更强的一致性用底层 `sdk.storage.get/set`（异步、直达 plugin-storage），\n更大的量走「T2 侧持有 + rpc 分页取」。\n\n## 功能区（suite section）最小用例\n\n第三方功能区 = manifest 的 `ui.suiteSection` 贡献 + 沙箱入口 html（多区插件各区用\n`decl.entry` 指各自入口——入口即身份，init 里没有 sectionId）。数据、导航、剪贴板全部走\nsuite 桥（宿主 typed 白名单 op）；素材图片 / 视频不过桥，用应答里的 `channek-media:`\ncapability URL 直接 `<img>`/`<video>` 加载。\n\n```ts\nimport {\n  createSandboxSdk,\n  suiteContextFromInit,\n  SuiteBridgeError,\n} from '@channek/sandbox-sdk';\n\nconst sdk = createSandboxSdk();\n\nsdk.onInit(async init => {\n  const context = suiteContextFromInit(init);\n  if (!context) return; // 非 suite 承载面（viewer/panel）：suite 桥会被宿主以 not-a-suite 拒\n  if (context.workspaceId === null) {\n    document.body.textContent = '当前没有打开的频道';\n    return;\n  }\n  try {\n    // ≡ 渲染 IPC workspace:assetsOverview 的 DTO（精确形状见下「类型怎么拿」）\n    const overview = await sdk.suite.assetsOverview();\n    render(overview);\n  } catch (error) {\n    if (error instanceof SuiteBridgeError && error.code === 'response-too-large') {\n      document.body.textContent = '素材库过大，等待分页支持';\n      return;\n    }\n    throw error;\n  }\n});\n\n// 导航（宿主逐条校验实参，防导航钓鱼）：\nawait sdk.suite.openContent('item-1');\nawait sdk.suite.openSection('homes');\n\n// 剪贴板（沙箱 iframe 拿不到 navigator.clipboard，必须过桥）：\nawait sdk.suite.clipboardWriteText('复制的文案');\n```\n\n要点（第三方作者常踩）：\n\n- **类型怎么拿**：读 op 应答在线上按 `unknown` 承载；精确 DTO\n  （`WorkspaceAssetsOverviewDTO` / `WorkspaceOperationsSnapshotDTO`）从\n  `@channek/ipc-contract` **类型层** import（`import type`），运行时保持零依赖。\n- **错误分支认 `SuiteBridgeError.code`**（`unknown-op` / `not-a-suite` /\n  `response-too-large` / `permission-denied` / `consent-required` / `timeout` /\n  `invalid-payload` / `rate-limited` / `host-error`），别 parse message 文本——文案不冻结，\n  码才冻结。\n- **写 op（`sdk.suite.saveBacklog` / `sdk.suite.updateField`）**：manifest 要声明\n  `workspace:write`（缺 → `permission-denied`）；每 (插件, 频道) 首次写会得\n  `consent-required`，同时宿主弹一次同意确认——把它渲染成「等待用户允许后重试」，别当致命\n  错误。revision 冲突以人类可读错误回来（`host-error`），刷新快照（`operationsSnapshot`）\n  取新 revision 再重试。\n- **剪贴板本地先限流**：文本 ≤32KiB、每分钟 ≤10 次（`SUITE_CLIPBOARD_LIMITS`），超限\n  SDK 直接拒、不出桥——宿主那侧的限额是审计面，不是你的节流器。\n- **大清单自己虚拟滚动**：读应答上限 1MiB（`SUITE_RESPONSE_LIMITS`），超限宿主拒并提示\n  分页；iframe 内的渲染性能是插件职责。\n","readmeFilename":"README.md"}