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

@youmind-ai/openapi-bridge

v1.0.0

Published

让不受信的页面运行时调用 YouMind OpenAPI(`/openapi/v1/*`),同时保证 **API Key 自始至终不进入页面代码**。桥本身与具体业务无关;当前用例是 AI 生成的网页(Webpage craft)。覆盖两个运行环境,页面代码完全同一份:

Readme

@youmind-ai/openapi-bridge

让不受信的页面运行时调用 YouMind OpenAPI(/openapi/v1/*),同时保证 API Key 自始至终不进入页面代码。桥本身与具体业务无关;当前用例是 AI 生成的网页(Webpage craft)。覆盖两个运行环境,页面代码完全同一份:

  • 生产:youweb 的 sandbox iframe,经 postMessage 桥由宿主代为执行;
  • 本地开发(youdesktop 沙箱等):经本地凭证代理(dev proxy)执行。

环境判别只有一条规则:读到 dev 代理配置即开发环境,否则即生产环境;两种环境下失败都直接报错,没有回退和探测。

工作原理

iframe(不受信代码)                      youweb(宿主)                         youapi
┌─────────────────────┐   MessageChannel  ┌──────────────────────┐   HTTPS   ┌────────┐
│ youmind.fetch(path) │ ────────────────► │ executor:            │ ────────► │ openapi│
│  只传 path/method/  │ ◄──────────────── │  路径白名单校验       │ ◄──────── │  /v1/* │
│  headers/body       │   序列化的响应     │  剥离敏感头           │           └────────┘
└─────────────────────┘                   │  附加 x-api-key      │
                                          └──────────────────────┘

握手为三步:client 携带全新 MessagePort 定时重试 connect → 宿主校验 event.source 是所服务 iframe 的 contentWindow 后回 ready → client 只采用第一条 ready 的通道并回 ack,宿主只把收到 ack 的通道设为活跃通道。此后请求/响应全部走该 port,window 级消息只用于握手。

安全边界:

  • 凭证隔离:API Key 只存在于宿主侧 executor 闭包内,协议消息里没有任何凭证字段;
  • 路径与方法白名单:默认 executor 只放行 /openapi/v1/ 下的 GET / POST;路径只做一次百分号解码,残留编码、点分段、反斜杠、双斜杠、控制字符与 fragment 一律拒绝;
  • 宿主头所有权:iframe 传来的凭证头、Host/CORS/长度/编码头与逐跳连接头一律丢弃;缓冲响应同样剥离过期长度/编码头与逐跳头;
  • 来源校验:宿主只接受指定 iframe contentWindow 发来的握手,兼容现有 sandbox(无 allow-same-origin,opaque origin)模型;
  • 资源边界:默认最多保留 4 条待 ack 通道与 8 个并发请求;请求头 128 个/64 KiB、请求体 16 MiB、响应头 128 个/64 KiB、响应体 32 MiB,单请求最长 30 秒;limits 只能把这些上限调低;
  • 运行时校验:MessagePort 上的消息不信任 TypeScript 类型;畸形请求、重复 request id、超限请求均在调用 executor 前拒绝;
  • 错误脱敏:回传 iframe 的错误只含 name/message,不带 stack。

用法

宿主侧(youweb)

import { attachOpenApiBridge, createOpenApiExecutor } from '@youmind-ai/openapi-bridge/host';

const bridge = attachOpenApiBridge(iframeElement, {
  execute: createOpenApiExecutor({
    baseUrl: apiBaseUrl,
    // API Key 的获取/缓存/轮换策略由 youweb 决定,每次请求时调用
    getApiKey: () => fetchScopedApiKey(),
  }),
});
// iframe 卸载时必须释放
bridge.dispose();

需要完全自定义执行逻辑时可不用 createOpenApiExecutor,直接实现 BridgeRequestExecutor;实现必须响应 context.signal,使宿主的取消、重连、dispose 与 30 秒超时能及时终止底层工作。

iframe 侧(生成的网页)

import { installOpenApiBridgeGlobal } from '@youmind-ai/openapi-bridge/client';

// 由 youweb 用自身构建打进注入脚本;页面代码只使用全局 youmind 对象
installOpenApiBridgeGlobal();

// 生成的网页代码:
const response = await youmind.fetch('/openapi/v1/listMaterials', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ boardId }),
});
const data = await response.json();

youmind.fetch 与标准 fetch 语义对齐:返回 Response、HTTP 错误状态不 reject、支持 AbortSignal、body 支持 string / Blob / ArrayBuffer / FormData / URLSearchParams 等非流式类型。第一个参数只接受以 / 开头的站内路径,目标域名由宿主决定。

环境判别与页面侧形态

运行时不做环境嗅探,也没有任何硬编码端点。判别式是开发环境注入的代理配置:配置存在 → 开发(直连该端点,ready 阶段一次健康检查,不可达报错并提示启动 youmind proxy);配置不存在 → 生产(postMessage 桥,宿主未应答/版本不匹配报错)。

页面侧在两个环境加载的东西并不相同,但暴露同一个全局接口契约:

youmind.fetch(path, init): Promise<Response>; // path 为 / 开头的站内路径,语义与 fetch 对齐
youmind.ready: Promise<void>;
  • 生产:youweb 用自身构建把 @youmind-ai/openapi-bridge/client 打进注入脚本,调用 installOpenApiBridgeGlobal();无配置 → 桥;
  • 开发:页面不加载本包youmind proxy --write-config 按代理实际监听地址写出一个小型静态 shim(youmind.dev.js,模板见下文「本地开发」),直接定义同形的 youmind 全局并写入 __YOUMIND_BRIDGE_CONFIG__,页面一个 <script src="/youmind.dev.js"> 引用即可。

createOpenApiBridgeClient 的配置判别是这条规则的权威实现(解析顺序:显式 options.endpoint > __YOUMIND_BRIDGE_CONFIG__.endpoint > 无 → 桥);dev shim 是它在开发环境的极简等价物——白名单、凭证、头剥离等所有真正的规则都在代理/宿主侧服务端强制,页面侧再简单也破坏不了契约。installOpenApiBridgeGlobal 幂等:先装先赢(哨兵 __youmindBridgeClient,dev shim 也参与同一哨兵),页面自带的 install 调用不会覆盖环境注入的选择;前提是环境注入方把自己置于 <head> 顶部、先于一切页面脚本执行——youweb srcDoc 与发布管线都由我们控制注入位置,必须遵守。

本地开发(youdesktop 沙箱)

dev 代理由 youdesktop 的 Rust youmind proxy 提供(凭证读沙箱 trusted_env 注入的 YOUMIND_API_KEY + YOUMIND_BASE_URL);本包不含代理实现,只定义契约:协议常量(PROTOCOL_VERSION / DEV_PROXY_HEALTH_PATH / DEV_PROXY_SERVICE_NAME / DEFAULT_DEV_PROXY_PORT)+ 客户端健康检查 + 下述安全边界。健康检查必须返回 {"service":"youmind-openapi-bridge-proxy","version":1},完整 client 与静态 shim 都同时校验 service 和 protocol version;缺字段或版本不匹配时 ready 必须 reject,不得发送业务请求。

dev 侧的全部脚手架 = 起代理 + 写 shim(--write-config,按实际监听地址生成,端口任意),页面加一个 <script src="/youmind.dev.js">。shim 的权威模板:

// youmind.dev.js — generated by `youmind proxy --write-config`; never ship to production.
(function () {
  var g = globalThis;
  // 防泄漏保障一:非回环源不安装——即使被误发布,shim 在生产域名下直接 no-op
  var host = g.location && g.location.hostname;
  if (host !== 'localhost' && host !== '127.0.0.1' && host !== '[::1]') return;
  // 防泄漏保障二:与完整运行时共用安装哨兵,先装先赢(环境注入方位于 head 顶部先行执行)
  if (g.__youmindBridgeClient) return;
  var endpoint = 'http://127.0.0.1:9686'; // 代理实际监听地址,由代理写入
  var protocolVersion = 1; // 与 @youmind-ai/openapi-bridge 的 PROTOCOL_VERSION 同步
  g.__YOUMIND_BRIDGE_CONFIG__ = { endpoint: endpoint };
  function unavailable() {
    return new Error('OpenAPI bridge dev proxy is not reachable at ' + endpoint + '. Ensure `youmind proxy` is running.');
  }
  var ready = fetch(endpoint + '/.youmind-bridge/health')
    .catch(function () { throw unavailable(); })
    .then(function (response) {
      if (!response.ok) throw unavailable();
      return response.json().catch(function () { throw unavailable(); });
    })
    .then(function (health) {
      if (!health || health.service !== 'youmind-openapi-bridge-proxy') throw unavailable();
      if (health.version !== protocolVersion) {
        throw new Error(
          'OpenAPI bridge protocol version mismatch: proxy=' +
            String(health.version) +
            ', client=' +
            protocolVersion,
        );
      }
    });
  ready.catch(function () {}); // 避免页面只用 fetch 前出现 unhandled rejection
  var client = {
    fetch: function (path, init) {
      if (typeof path !== 'string' || path.charAt(0) !== '/') {
        return Promise.reject(new TypeError('OpenAPI bridge fetch only accepts an in-site path starting with "/"'));
      }
      return ready.then(function () { return fetch(endpoint + path, init); });
    },
    ready: ready,
    dispose: function () {},
  };
  g.__youmindBridgeClient = client;
  g.youmind = { fetch: client.fetch, ready: client.ready };
})();

防泄漏是三重机制,不是约定——Vite 等构建工具会把 public/ 原样拷进产物,"生产不携带该文件"不能只靠嘱咐:

  1. shim 自禁用:非回环 hostname 直接 return(生产 srcDoc iframe 的 hostname 为空,同样命中),泄漏文件在生产环境是死代码;
  2. 安装哨兵:shim 与 installOpenApiBridgeGlobal 共用 __youmindBridgeClient,先装先赢;环境注入方(youweb srcDoc / 发布管线)必须把注入脚本置于 <head> 顶部、先于一切页面脚本执行;
  3. 发布/入库管线按标记(文件名 youmind.dev.js 或首行生成标记注释)机械剔除该文件与其 script 标签。

dev server 页面、无头浏览器自动化测试直接可用;Node 侧测试可注入 __YOUMIND_BRIDGE_CONFIG__ 后使用本包 client,或直接 fetch 代理。DEFAULT_DEV_PROXY_PORT(9686)只是代理服务器自己的默认监听端口,页面侧从不引用它。

代理实现必须满足的契约(macOS 沙箱不是网络命名空间,回环端口对整机可见,必须自防):

  • 网络边界:仅绑定 127.0.0.1;Host 头必须是回环地址(DNS rebinding 防御);Origin 存在且非 localhost 族 → 403 且不带任何 CORS 头;
  • CORS(浏览器强制,必须实现):页面源(如 http://localhost:5173)与代理跨源,JSON POST 会触发 OPTIONS 预检。对回环 Origin:预检回 204 + Access-Control-Allow-Origin: <反射该 Origin> + Access-Control-Allow-Methods: GET, POST, OPTIONS + Access-Control-Allow-Headers: <反射 Access-Control-Request-Headers> + Access-Control-Max-Age;所有已通过回环 Origin 校验的响应——含健康检查与 403/413/502 错误——都带 Access-Control-Allow-Origin 反射与 Vary: Origin;被 Origin gate 拒绝的非回环请求仍返回不带 CORS 头的 403;不发 Allow-Credentials(凭证由代理侧附加,浏览器凭证无关);
  • 请求处理:只转发 /openapi/v1/ 下的 GET / POST;路径白名单与凭证/宿主头/逐跳头处理和生产宿主同规则(参照 createOpenApiExecutor 与 host 的传输层剥离逻辑);请求头 64 KiB、请求体 16 MiB、响应体 32 MiB,上游请求 30 秒超时;响应剥离过期与逐跳头后按缓冲字节重写 content-length

反模式警告:不要用 VITE_* / NEXT_PUBLIC_* 之类 build-time env 把 YOUMIND_API_KEY 注入页面——key 会被烘进构建产物,页面一发布就泄漏。凭证只应存在于代理进程里。

集成注意

本包只提供机制,youweb / 生成侧的接线是后续工作:

  • client 脚本进入 iframe:本包不含注入逻辑。srcDoc 场景需要 youweb 用自身构建把 client 打成内联注入脚本(可参考现有 injectContextInfo 的做法,但那条链路目前只注入 window.contextInfo,且只覆盖有 context 的 srcDoc 分支);URL 直连 CDN 的场景需要生成侧在 HTML 里内置该脚本。开发环境页面不加载本包(见上文 shim)。未注入的页面里不存在全局 youmind
  • attach 绑定 iframe 元素实例:React 重挂载出新 iframe 元素(如 context / url 变化触发重建)时,旧桥必须 dispose() 并对新元素重新 attachOpenApiBridge,否则 event.source 校验永远不匹配。建议在 ref callback / effect 中随元素生命周期管理。
  • 只能挂在无 allow-same-origin 的 sandbox iframe 上(现有 Webpage 渲染即如此):桥的凭证隔离依赖 iframe 处于 opaque origin;把桥挂到非沙箱或 same-origin 的 frame 等于把 API Key 能力交给其中的任意代码。
  • youapi 的 CORS 配置需允许 youweb origin 携带 x-api-key 头访问 /openapi/v1/*
  • 响应整体缓冲后回传,不支持流式响应 / SSE;不适合超大响应体。

入口

| 入口 | 内容 | 使用方 | |------|------|--------| | @youmind-ai/openapi-bridge | 协议类型与常量 | 各方共享 | | @youmind-ai/openapi-bridge/client | installOpenApiBridgeGlobal / createOpenApiBridgeClient / createIframeBridgeClient / createHttpBridgeClient | 页面内 | | @youmind-ai/openapi-bridge/host | attachOpenApiBridge / createOpenApiExecutor | youweb |

client 与 host 入口刻意分离,避免宿主逻辑被打进注入页面的脚本。包内全部是浏览器代码;dev 代理的实现在 youdesktop 的 youmind CLI,本包只提供其行为契约(协议常量与本 README)。