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

electron-renderer-fetch

v0.0.1

Published

在 Electron 主进程里复用某个 BrowserWindow 渲染进程的 window.fetch,便于在该窗口的 DevTools Network 面板里抓包/调试主进程发起的网络请求。可选 onlyWhenDevToolsOpen,未启用时透明 fallback 到 net.fetch。

Readme

@nlai/electron-renderer-fetch

在 Electron 主进程里复用某个 BrowserWindow 渲染进程的 window.fetch,便于在该窗口的 Chrome DevTools → Network 面板里抓主进程发起的网络请求。

主进程默认走的是 electron.net.fetch,这条网络请求是看不到的——既不会出现在任何窗口的 DevTools 里,也不会被 Chromium 抓到。本库通过 IPC 把主进程的 fetch 调用「转发」到一个已注册的渲染进程里执行真正的网络 IO,请求自然就出现在该窗口的 Network 面板了。

特性

  • 提供一个完全标准的 fetch 函数(签名兼容 globalThis.fetch)。
  • onlyWhenDevToolsOpen: true:只有当目标窗口打开了 DevTools 时才走代理,平时零开销 fallback 到 net.fetch,生产环境无感知。
  • 流式响应支持(SSE / OpenAI streaming),Response.bodyReadableStream<Uint8Array>
  • AbortSignal 双向取消(主→渲染、渲染→主、超时、stream.cancel())。
  • 单次请求超时 (requestTimeoutMs)。
  • body 不可序列化(如 FormData / ReadableStream)时自动 fallback 而不是静默丢 body。
  • RequestInit 透传:method / headers / body / signal / credentials / mode / redirect / cache / referrer / referrerPolicy / integrity / keepalive / priority
  • 渲染进程崩溃 / 关闭时,已在飞的请求会立刻收到 AbortError,不会悬挂。

行为差异需要心里有数

主进程 net.fetch 与渲染进程 window.fetch 走的是两套网络栈:

| 维度 | net.fetch (main) | window.fetch (renderer) | | --- | --- | --- | | Cookie jar | 主进程 session | 该窗口的 partition | | Proxy 设置 | 主进程 session | 渲染进程 session | | User-Agent | Electron UA | Chromium UA(含窗口注入) | | TLS / 证书豁免 | net 层 | webSecurity 设置 |

切换到 renderer fetch 的请求行为会有微小差异,这是为了能在 DevTools 看到请求所付的代价。只在调试场景下使用代理(默认配合 onlyWhenDevToolsOpen: true)就能避免影响生产。

安装

作为 monorepo workspace 包使用时,在使用方的 package.json 里加:

{
  "dependencies": {
    "@nlai/electron-renderer-fetch": "workspace:*"
  }
}

然后用你项目当前的包管理器安装(pnpm / yarn / npm 任一)即可。本仓库使用 pnpm。

⚠️ 注意:本包是 ESM + TS 源码 + 子入口导出,必须让 electron-vite 把它一起 bundle,不能走 externalize。否则 Node 直接 import .ts 会失败。

electron.vite.config.tsmainpreload 两段都把它加进 externalizeDepsPluginexclude 列表:

main: {
  plugins: [
    externalizeDepsPlugin({
      exclude: ['@nlai/electron-renderer-fetch' /* ... */]
    })
  ]
},
preload: {
  plugins: [
    externalizeDepsPlugin({
      exclude: ['@nlai/electron-renderer-fetch' /* ... */]
    })
  ]
}

用法

整套链路只有 3 步

1. preload:注册当前窗口

// src/preload/index.ts
import { setupRendererFetchProxy } from '@nlai/electron-renderer-fetch/preload'

setupRendererFetchProxy()

调用一次即可,幂等。所有用了同一份 preload 的 BrowserWindow 都会自动注册成「可被代理的渲染进程」。

2. 主进程:创建代理 fetch

⚠️ 调用时机createRendererFetch(...) 必须在第一个 BrowserWindow 加载 preload 之前至少执行一次(IPC 监听器要先于 REGISTER 消息装好),否则首个窗口的 register 消息会被丢弃。

一般做法是在主进程模块的顶层就 import + 调用,让模块加载时即装好 listener;如果你只能在窗口创建之后才能拿到配置,可以提前调用一次 installRendererFetchMain() 占位(见 API 章节)。

// src/main/somewhere.ts
import { createRendererFetch } from '@nlai/electron-renderer-fetch/main'
import { windowService } from '@main/services/WindowService'

const myFetch = createRendererFetch({
  // 首选这个窗口;它没注册 / 已销毁时会按 fallback 处理
  preferredWebContents: () => windowService.getClawAgentsWindow()?.webContents,

  // 推荐生产开启:仅在 DevTools 打开时才走代理,平时直接走 net.fetch
  onlyWhenDevToolsOpen: true,

  // 可选:单次请求超时(含等待 meta 与流式接收的整段时间)
  requestTimeoutMs: 5 * 60 * 1000,

  // 可选:自定义 fallback(默认 electron.net.fetch)
  // fallback: globalThis.fetch,

  // 可选:调用时如果还没渲染进程注册,最多等多少 ms 再 fallback
  // waitForRendererMs: 1000,

  // 可选:preferred 不可用时是否拒绝回退到其他注册窗口;默认 true(严格、安全)
  // strictPreferred: true
})

// 返回值是一个完全标准的 fetch
const res = await myFetch('https://api.example.com/v1/chat', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ ... })
})
const json = await res.json()

3. 把代理 fetch 注入到第三方 SDK

例如把它塞给 OpenAI / @agentx/harness

await harness.registerModelProvider({
  ...modelProviderConfig,
  request: {
    fetch: (_models, input, init) => myFetch(input, init)
  }
})

真实示例(本仓库 ai-studio)

// src/main/claw-agents/index.ts
import { createRendererFetch } from '@nlai/electron-renderer-fetch/main'
import { windowService } from '@main/services/WindowService'

const clawAgentsRendererFetch = createRendererFetch({
  preferredWebContents: () => windowService.getClawAgentsWindow()?.webContents,
  onlyWhenDevToolsOpen: true,
  requestTimeoutMs: 5 * 60 * 1000
})

await harness.registerModelProvider({
  ...modelProviderConfig,
  request: {
    fetch: async (_models, input, init) => clawAgentsRendererFetch(input, init)
  }
})

调试方式:打开 ClawAgents 窗口 → F12 → 切到 Network 面板 → 触发一次模型请求,就能看到对应 HTTP 请求的 headers / body / response / timing。关掉 DevTools 后下次请求自动回到 net.fetch,不再产生 IPC 开销。

API

主进程 — import ... from '@nlai/electron-renderer-fetch/main'

createRendererFetch(options?: RendererFetchOptions): typeof fetch

返回一个标准 fetch。优先把请求转发到 preferredWebContents() 返回的渲染进程;不可用时走 fallback

type RendererFetchOptions = {
  /**
   * 返回首选的 webContents。
   * 指定后默认严格使用——不可用时直接 fallback,避免请求被悄悄发到无关窗口。
   */
  preferredWebContents?: () => WebContents | null | undefined

  /**
   * 当 `preferredWebContents` 返回的 webContents 不可用时是否拒绝回退到其他已注册窗口。
   * 默认 true(严格模式,安全)。设为 false 才会获得「任意可用 wc 都行」的旧行为。
   * 当未提供 `preferredWebContents` 时此选项被忽略。
   */
  strictPreferred?: boolean

  /**
   * 仅当目标 webContents 已打开 DevTools 时才走代理;未打开则 fallback。
   * 推荐生产环境开启,做到「打开 DevTools 才抓包」、平时零开销。
   */
  onlyWhenDevToolsOpen?: boolean

  /** 没有可用渲染进程时使用的兜底 fetch,默认 `electron.net.fetch`。 */
  fallback?: typeof fetch

  /** 调用时若没有可用渲染进程,最长等待多少毫秒;默认 0(不等待,直接 fallback)。 */
  waitForRendererMs?: number

  /** 单次请求超时时间(毫秒);0 表示不限制。包括等待 meta 与流式接收的整段时间。 */
  requestTimeoutMs?: number
}

hasRegisteredRendererFetch(): boolean

是否有任意已注册、未销毁、未崩溃的 webContents(不考虑 DevTools 是否打开)。

installRendererFetchMain(): void

显式安装主进程侧 IPC 监听器。createRendererFetch() 内部会自动调用,因此通常不需要直接用它。

只在「需要 lazy 创建 fetch、但要确保不丢失 preload register 消息」的场景下才有用:在 app 顶层尽早调用一次 installRendererFetchMain() 即可消除该竞态。多次调用幂等。

Preload — import ... from '@nlai/electron-renderer-fetch/preload'

setupRendererFetchProxy(): void

把当前 webContents 注册为主进程的 fetch 代理。多次调用是幂等的。

teardownRendererFetchProxy(): void

注销 + 取消所有未完成请求。一般用于测试 / HMR。

共享 — import ... from '@nlai/electron-renderer-fetch/shared'

导出 IPC channel 名常量与序列化类型,仅在你需要做特殊扩展(比如自定义 transport)时才会用到。

工作原理

  ┌─────────────── main process ───────────────┐
  │                                            │
  │  createRendererFetch({preferredWC, ...})   │
  │           │                                │
  │           │ targetWc.send(REQUEST)         │
  │           ▼                                │
  └─────── IPC ────────────────────────────────┘
              │
              ▼
  ┌─────────── renderer process (preload) ─────┐
  │                                            │
  │  ipcRenderer.on(REQUEST)                   │
  │      → window.fetch(url, init)             │
  │      → 把 meta + chunks 流式 send 回 main   │
  │                                            │
  └────────────────────────────────────────────┘
              │
              ▼ Network 面板里看得见的 HTTP 请求
            外网

主进程拿到 Response 后构造一个 ReadableStream<Uint8Array>,IPC 收到的每个 chunk 实时 enqueue 进去,所以业务代码可以无感地用流式 API(response.body.getReader() / response.text() / SSE 解析)。

限制 / 不支持的场景

  • bodyFormData / ReadableStream 时会自动 fallback 到 net.fetch(IPC 不能稳定序列化它们)。
  • IPC 每个 chunk 一次 send,对每秒上千 chunk 的极快流可能有 IPC 开销上的额外消耗(OpenAI streaming 远低于这个量级,无影响)。
  • 渲染进程的 cookies / proxy / UA 与主进程不一致,需要严格行为一致时不要开启 onlyWhenDevToolsOpen: false 让它在生产里持续生效。

License

Internal use only (workspace package).