mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-31 10:06:33 +00:00
Review of T0 (Electron scaffold) approved with one Important finding + cheap
Minors. All fixes scoped to app/.
- usePolled: replace per-call cancel closure with a generation/epoch counter so
an orphaned in-flight fetch (from refresh() or an interval tick, discarded
cancel handle) can't resolve after a newer fetch and clobber fresh data. New
TDD race test (slow deps-A resolves after fast deps-B; keeps B).
- cli: nvm resolution now scans version dirs descending and takes the first
whose bin actually contains codeburn (was lexicographic max, unverified),
matching CodeburnCLI.swift. Export nodeManagerDirs + hermetic nvm test.
- index.html: tighten CSP connect-src ws: -> ws://localhost:5173.
- preload: import type { Envelope } from main instead of redeclaring it.
- main.test: table-driven channel->argv assertion over all 9 codeburn:* channels
plus a keys check and a non-spawning cliStatus case.
39 lines
1.8 KiB
TypeScript
39 lines
1.8 KiB
TypeScript
import { contextBridge, ipcRenderer } from 'electron'
|
|
|
|
// Handlers resolve with { ok, value } | { ok, error } so the structured error
|
|
// `kind` survives the contextBridge boundary. `import type` is erased at build,
|
|
// so this shares main.ts's declaration without pulling its runtime in.
|
|
import type { Envelope } from './main'
|
|
|
|
async function invoke<T>(channel: string, ...args: unknown[]): Promise<T> {
|
|
const res = (await ipcRenderer.invoke(channel, ...args)) as Envelope<T>
|
|
if (res.ok) return res.value
|
|
// Reject with a plain object so `kind` is preserved (Error subclasses lose
|
|
// custom fields when cloned across worlds).
|
|
return Promise.reject(res.error)
|
|
}
|
|
|
|
// Shape matches CodeburnBridge (app/renderer/lib/types.ts); typing is enforced
|
|
// renderer-side where `window.codeburn` is declared as CodeburnBridge.
|
|
const bridge = {
|
|
getOverview: (period: string, provider: string) => invoke('codeburn:getOverview', period, provider),
|
|
getPlans: (period: string) => invoke('codeburn:getPlans', period),
|
|
getModels: (period: string, provider: string, byTask: boolean) => invoke('codeburn:getModels', period, provider, byTask),
|
|
getYield: (period: string) => invoke('codeburn:getYield', period),
|
|
getSpendFlow: (period: string, provider: string) => invoke('codeburn:getSpendFlow', period, provider),
|
|
getDevices: (period: string) => invoke('codeburn:getDevices', period),
|
|
getShareStatus: () => invoke('codeburn:getShareStatus'),
|
|
getIdentity: () => invoke('codeburn:getIdentity'),
|
|
cliStatus: () => invoke('codeburn:cliStatus'),
|
|
}
|
|
|
|
const events = {
|
|
onRefresh: (cb: () => void): (() => void) => {
|
|
const listener = () => cb()
|
|
ipcRenderer.on('codeburn:refresh', listener)
|
|
return () => ipcRenderer.removeListener('codeburn:refresh', listener)
|
|
},
|
|
}
|
|
|
|
contextBridge.exposeInMainWorld('codeburn', bridge)
|
|
contextBridge.exposeInMainWorld('codeburnEvents', events)
|