-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathpreload.js
More file actions
207 lines (201 loc) · 12.4 KB
/
Copy pathpreload.js
File metadata and controls
207 lines (201 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("api", {
// Renderer-ready sentinel for the --smoke-test launch mode. boot.js
// calls this once boot() resolves; main's smoke path waits for it and
// exits 0. Fire-and-forget; ignored when not in smoke mode.
signalReady: () => { try { ipcRenderer.send("smoke:ready"); } catch {} },
// Boot-trace breadcrumb for --smoke-test, over IPC (the channel proven to
// reach main even on headless CI — console-message capture is unreliable
// there). main logs each only while smoke-testing; a no-op otherwise.
smokeTrace: (label) => { try { ipcRenderer.send("smoke:trace", String(label)); } catch {} },
loadPrefs: () => ipcRenderer.invoke("prefs:load"),
savePrefs: (d) => ipcRenderer.invoke("prefs:save", d),
env: () => ipcRenderer.invoke("env:get"),
// The build-baked cohort key (role=cohort_app), read once synchronously so the
// evidence reader can resolve it at module-eval. Empty on un-provisioned /
// public builds. A read-only value, not a function — there is nothing to invoke.
cohortKey: (() => { try { return ipcRenderer.sendSync("cohort-key:get"); } catch { return ""; } })(),
openExternal: (url) => ipcRenderer.invoke("shell:openExternal", url),
// Whole-window zoom — the renderer calls this for Cmd/Ctrl +/-/0 when it is
// NOT on a cohort view (cohort views handle those keys with their own scoped
// zoom). action: "in" | "out" | "reset".
appZoom: (action) => ipcRenderer.invoke("os:app-zoom", action),
loadContextVault: () => ipcRenderer.invoke("context-vault:manifest"),
scanContextVault: () => ipcRenderer.invoke("context-vault:scan"),
readContextVaultSource: (id) => ipcRenderer.invoke("context-vault:read-source", id),
readContextVaultRawBundle: () => ipcRenderer.invoke("context-vault:read-raw-bundle"),
revealContextVaultSource: (id) => ipcRenderer.invoke("context-vault:reveal-source", id),
revealContextVaultCorpus: () => ipcRenderer.invoke("context-vault:reveal-corpus"),
clipboardWrite: (text) => ipcRenderer.invoke("clipboard:write", text),
// ─── deep links (sros://xxxxx) ───────────────────────────────────────
// main forwards a clicked sros:// link here while the app is running;
// getPendingDeepLink drains a link that cold-launched the app (queued in
// main before the renderer's listener existed). See main.js open-url /
// second-instance → deliverDeepLink, and boot.js setupShareLinks.
onDeepLink: (cb) => {
const h = (_e, url) => { try { cb(url); } catch {} };
ipcRenderer.on("deep-link", h);
return () => ipcRenderer.removeListener("deep-link", h);
},
getPendingDeepLink: () => ipcRenderer.invoke("deep-link:get-pending"),
// app updates (electron-updater + GitHub Releases; no-op in dev)
checkAppUpdate: () => ipcRenderer.invoke("fg:check-update"),
applyAppUpdate: () => ipcRenderer.invoke("fg:apply-update"),
applyUpdateAndRestart: () => ipcRenderer.invoke("fg:apply-update-and-restart"),
// Manual-install path for unsigned mac builds: streams the platform's
// release asset to ~/Downloads/ and opens it (mac: shell.openPath →
// dmg mounts; linux/windows: reveals in Finder/Explorer). Returns
// { ok, path, version } so the renderer can show "downloaded · drag
// to /Applications" with the file path the user just got.
downloadAndRevealUpdate: () => ipcRenderer.invoke("fg:download-and-reveal-update"),
openDownloadedInstaller: (path) => ipcRenderer.invoke("shell:openDownloadedInstaller", path),
getAppInfo: () => ipcRenderer.invoke("fg:get-app-info"),
// Streams electron-updater's `download-progress` events (forwarded from
// main.js → "fg:update-progress") into the renderer so the inline update
// panel can render a % bar instead of leaving the user staring at a
// frozen button. `cb` receives the raw progress object from electron-
// updater: { percent, bytesPerSecond, transferred, total }.
onUpdateProgress: (cb) => {
const handler = (_e, p) => { try { cb(p); } catch {} };
ipcRenderer.on("fg:update-progress", handler);
return () => ipcRenderer.removeListener("fg:update-progress", handler);
},
// Streams main's periodic-check hits ("fg:update-available") so the
// renderer can light the update indicator + raise a toast without the
// user ever clicking the version stamp. `cb` receives { version }.
onUpdateAvailable: (cb) => {
const handler = (_e, info) => { try { cb(info); } catch {} };
ipcRenderer.on("fg:update-available", handler);
return () => ipcRenderer.removeListener("fg:update-available", handler);
},
// calendar export — PNG (recommended for messaging) or PDF.
exportCalendar: (opts) => ipcRenderer.invoke("fg:export-calendar", opts),
// bundled swf-node supervisor — see apps/os/swf-node.js. The renderer
// can poll getSwfNodeStatus() for a one-shot read, or subscribe via
// onSwfNodeStatus(cb) to a stream of state changes (idle | starting |
// running | crashed | unsupported). The returned function detaches
// the listener — call it on unmount.
getSwfNodeStatus: () => ipcRenderer.invoke("fg:swf-node-status"),
// Explicitly (re)spawn the daemon if it's down — for a manual "restart
// backend" affordance. Resolves { ok, status }.
restartSwfNode: () => ipcRenderer.invoke("fg:swf-node-restart"),
onSwfNodeStatus: (cb) => {
const handler = (_e, s) => { try { cb(s); } catch {} };
ipcRenderer.on("fg:swf-node-status-changed", handler);
return () => ipcRenderer.removeListener("fg:swf-node-status-changed", handler);
},
// When status === "external_squatter", returns `{ version, indrex }`
// describing the foreign daemon that grabbed :7777 before us. Null
// otherwise. Use to render a remediation banner.
getSwfNodeExternalInfo: () => ipcRenderer.invoke("fg:swf-node-external-info"),
// Agent bearer for swf-node's agent-gated routes (POST /sync/local_record
// primarily). Generated + persisted by apps/os/swf-node.js on first
// launch; main reads it from there. Returns null in dev mode without
// a bundled binary / on Windows / when SWF_NODE_DISABLE=1 — the
// renderer's sync-client falls back to the github PR path then.
getSwfAgentToken: () => ipcRenderer.invoke("fg:swf-agent-token"),
// ─── swarm mode (research-swarm subprocess) ─────────────────────────
// Lifecycle: getSwarmConfig → swarmConfigSet (first run) → swarmStart
// → consume swarmOutput stream → swarmStop (cancel) or wait for
// fg:swarm:status-changed { state: "idle", exitCode } final event.
swarmStatus: () => ipcRenderer.invoke("fg:swarm:status"),
swarmStart: (o) => ipcRenderer.invoke("fg:swarm:start", o || {}),
swarmStop: () => ipcRenderer.invoke("fg:swarm:stop"),
getSwarmConfig: () => ipcRenderer.invoke("fg:swarm:config:get"),
setSwarmConfig: (o) => ipcRenderer.invoke("fg:swarm:config:set", o || {}),
onSwarmOutput: (cb) => {
const h = (_e, p) => { try { cb(p); } catch {} };
ipcRenderer.on("fg:swarm:output", h);
return () => ipcRenderer.removeListener("fg:swarm:output", h);
},
onSwarmStatus: (cb) => {
const h = (_e, p) => { try { cb(p); } catch {} };
ipcRenderer.on("fg:swarm:status-changed", h);
return () => ipcRenderer.removeListener("fg:swarm:status-changed", h);
},
// ─── easel · NDI projection (apps/os/easel-ndi.js) ──────────────────
// listSources() enumerates screens/windows (main-side desktopCapturer);
// start() opens an NDI sender; frame() ships one RGBA frame (await for
// natural backpressure — main drops frames if a send is in flight);
// stats() reports { live, name, connections, frames }; stop() ends it.
easel: {
available: () => ipcRenderer.invoke("easel:available"),
listSources: () => ipcRenderer.invoke("easel:list-sources"),
start: (o) => ipcRenderer.invoke("easel:start", o || {}),
frame: (f) => ipcRenderer.invoke("easel:frame", f),
stats: () => ipcRenderer.invoke("easel:stats"),
stop: () => ipcRenderer.invoke("easel:stop"),
// Receive side — discover NDI sources on the LAN + stream the chosen
// source's video frames into the renderer. Returns a detach function.
findNdi: (o) => ipcRenderer.invoke("easel:find-sources", o || {}),
rxStart: (sourceName) => ipcRenderer.invoke("easel:rx-start", { sourceName }),
rxStop: () => ipcRenderer.invoke("easel:rx-stop"),
rxStats: () => ipcRenderer.invoke("easel:rx-stats"),
onRxFrame: (cb) => {
const handler = (_e, frame) => { try { cb(frame); } catch {} };
ipcRenderer.on("easel:rx-frame", handler);
return () => ipcRenderer.removeListener("easel:rx-frame", handler);
},
onRxAudio: (cb) => {
const handler = (_e, frame) => { try { cb(frame); } catch {} };
ipcRenderer.on("easel:rx-audio", handler);
return () => ipcRenderer.removeListener("easel:rx-audio", handler);
},
// Per-source low-bandwidth thumbnail receivers — drives the live
// previews inside each LAN feed card.
thumbStart: (sourceName) => ipcRenderer.invoke("easel:thumb-start", { sourceName }),
thumbStop: (sourceName) => ipcRenderer.invoke("easel:thumb-stop", { sourceName }),
thumbStopAll: () => ipcRenderer.invoke("easel:thumb-stop-all"),
onThumbFrame: (cb) => {
const handler = (_e, frame) => { try { cb(frame); } catch {} };
ipcRenderer.on("easel:thumb-frame", handler);
return () => ipcRenderer.removeListener("easel:thumb-frame", handler);
},
},
// ─── router (Teleport Router) ────────────────────────────────────────
// The router app runs in its OWN pop-out window (src/router/) behind its own
// shim preload (window.daybook). From the MAIN window we only need to open it
// — the apps card / command palette / onboarding step call this.
daybook: {
openWindow: () => ipcRenderer.invoke("daybook:open-window"),
},
// ─── matrix (cohort chat) ────────────────────────────────────────────
// The "chat" top-level tab (src/renderer/chat/) drives the main-process
// Matrix client. Invoke methods are request/response; on* subscribe to the
// live broadcast channels and return an unsubscribe fn (call on unmount).
matrix: {
status: () => ipcRenderer.invoke("matrix:get-status"),
flows: (hs) => ipcRenderer.invoke("matrix:flows", hs),
loginSSO: (p) => ipcRenderer.invoke("matrix:login-sso", p),
loginDevice: (p) => ipcRenderer.invoke("matrix:login-device", p),
loginCode: (p) => ipcRenderer.invoke("matrix:login-code", p),
loginAccessToken: (p) => ipcRenderer.invoke("matrix:login-access-token", p),
cancelSSO: () => ipcRenderer.invoke("matrix:cancel-sso"),
login: (p) => ipcRenderer.invoke("matrix:login", p),
loginToken: (p) => ipcRenderer.invoke("matrix:login-token", p),
logout: () => ipcRenderer.invoke("matrix:logout"),
rooms: () => ipcRenderer.invoke("matrix:get-rooms"),
messages: (roomId) => ipcRenderer.invoke("matrix:get-messages", roomId),
send: (roomId, body) => ipcRenderer.invoke("matrix:send", { roomId, body }),
onStatus: (cb) => {
const handler = (_e, s) => { try { cb(s); } catch {} };
ipcRenderer.on("matrix:status", handler);
return () => ipcRenderer.removeListener("matrix:status", handler);
},
onRooms: (cb) => {
const handler = (_e, r) => { try { cb(r); } catch {} };
ipcRenderer.on("matrix:rooms", handler);
return () => ipcRenderer.removeListener("matrix:rooms", handler);
},
onMessages: (cb) => {
const handler = (_e, m) => { try { cb(m); } catch {} };
ipcRenderer.on("matrix:messages", handler);
return () => ipcRenderer.removeListener("matrix:messages", handler);
},
},
});
// Earliest possible boot breadcrumb: preload runs before any renderer script.
// If main (during --smoke-test) logs "cp:preload" but no later checkpoints, the
// page's module graph is what hangs; if even this never arrives, the renderer
// process isn't executing JS at all. No-op outside smoke (no listener).
try { ipcRenderer.send("smoke:trace", "preload"); } catch {}