moeru-ai/airi · error · Error

Electron ipcRenderer is not available. Pass it explicitly…

Error message

Electron ipcRenderer is not available. Pass it explicitly to useElectronEventaContext().

What it means

useElectronEventaContext()/getElectronEventaContext() in packages/electron-vueuse builds an eventa context over Electron IPC. It first uses an explicitly passed ipcRenderer, otherwise falls back to window.electron.ipcRenderer, the conventional bridge a contextBridge preload script exposes under contextIsolation. If neither exists it throws this error, meaning the code is running outside an Electron renderer with a properly configured preload (plain web app, SSR, test environment, or a preload that did not expose ipcRenderer). The shared context is memoized once, so the first successful resolution wins for the whole app.

Solutions

  1. Ensure the Electron preload exposes the bridge: contextBridge.exposeInMainWorld('electron', { ipcRenderer }) and that this preload is attached in the BrowserWindow webPreferences.
  2. If you are in an Electron renderer with a non-standard bridge, pass ipcRenderer explicitly: useElectronEventaContext(window.myIpc).
  3. If the code can run outside Electron (web build, tests), guard the call with a window.electron?.ipcRenderer check and use a different transport/context on that branch.
  4. Defer useElectronEventaContext() to component setup / client-only code instead of module top-level so SSR and test bundles never evaluate it.

Example fix

// before
const ctx = useElectronEventaContext() // module scope; throws in web/tests

// after
const hasBridge = typeof window !== 'undefined' && !!window.electron?.ipcRenderer
const ctx = hasBridge
  ? useElectronEventaContext()
  : undefined // use a web eventa context (e.g. BroadcastChannel) instead
Defensive patterns

Strategy: validation

Validate before calling

const ipc = (globalThis as any).window?.electron?.ipcRenderer
if (!ipc && !import.meta.env.ELECTRON) {
  // running outside Electron: use a web transport instead of useElectronEventaContext()
}

Type guard

function hasExposedIpcRenderer(g: typeof globalThis): g is typeof globalThis & { window: { electron: { ipcRenderer: object } } } {
  const w = (g as { window?: unknown }).window
  return typeof w === 'object' && w !== null
    && typeof (w as any).electron?.ipcRenderer === 'object'
}

Try / catch

try {
  const ctx = useElectronEventaContext()
} catch {
  // no preload bridge: fall back to a non-Electron eventa context or fail loudly in dev
}

Prevention

When it happens

Trigger: Calling useElectronEventaContext() with no argument in a Vue app running in a plain browser tab or in vitest/happy-dom; running in an Electron renderer whose preload does not do contextBridge.exposeInMainWorld('electron', { ipcRenderer }); preload disabled via sandbox/contextIsolation misconfiguration; module evaluated during SSR where window.electron does not exist.

Common situations: A shared composable package consumed by both stage-web (browser) and stage-tamagotchi (Electron) — the browser build hits it; a refactor moved eventa context creation to module top-level so it evaluates in tests; the preload script was renamed or the exposeInMainWorld key changed.

Related errors


AI-assisted analysis of moeru-ai/airi@677329427f (2026-08-18). Data as JSON: /api/errors/d1b924681279cd53. Report an issue: GitHub.

Appendix: source

Thrown at packages/electron-vueuse/src/composables/use-electron-eventa-context.ts:20

import type { ShallowRef } from 'vue'

import { defineInvoke } from '@moeru/eventa'
import { createContext } from '@moeru/eventa/adapters/electron/renderer'
import { shallowRef } from 'vue'

type EventaContext = ReturnType<typeof createContext>['context']
type IpcRendererLike = Parameters<typeof createContext>[0]

let sharedContext: EventaContext | undefined

function resolveIpcRenderer(ipcRenderer?: IpcRendererLike): IpcRendererLike {
  if (ipcRenderer) {
    return ipcRenderer
  }

  const globalIpcRenderer = (globalThis as { window?: { electron?: { ipcRenderer?: IpcRendererLike } } }).window?.electron?.ipcRenderer
  if (!globalIpcRenderer) {
    throw new Error('Electron ipcRenderer is not available. Pass it explicitly to useElectronEventaContext().')
  }

  return globalIpcRenderer
}

export function getElectronEventaContext(ipcRenderer?: IpcRendererLike): EventaContext {
  sharedContext ??= createContext(resolveIpcRenderer(ipcRenderer)).context
  return sharedContext
}

export function useElectronEventaContext(ipcRenderer?: IpcRendererLike): ShallowRef<EventaContext> {
  return shallowRef(getElectronEventaContext(ipcRenderer))
}

export function useElectronEventaInvoke<Res, Req = undefined, ResErr = Error, ReqErr = Error>(invoke: InvokeEventa<Res, Req, ResErr, ReqErr>, context?: EventaContext) {
  return defineInvoke(context ?? getElectronEventaContext(), invoke)
}

View on GitHub (pinned to 677329427f)