bunite-core 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/package.json +7 -6
  2. package/src/{bun → host}/core/App.ts +45 -81
  3. package/src/{bun → host}/core/BrowserView.ts +64 -64
  4. package/src/{bun → host}/core/BrowserWindow.ts +14 -14
  5. package/src/host/core/Socket.ts +98 -0
  6. package/src/host/core/SurfaceBrowserIPC.ts +7 -0
  7. package/src/host/core/SurfaceManager.ts +154 -0
  8. package/src/host/encryptedPipe.ts +62 -0
  9. package/src/{bun → host}/events/appEvents.ts +0 -1
  10. package/src/host/index.ts +29 -0
  11. package/src/{bun/proc → host}/native.ts +38 -52
  12. package/src/{shared → host}/paths.ts +20 -26
  13. package/src/{bun/preload/inline.ts → host/preloadBundle.ts} +2 -2
  14. package/src/host/serveWeb.ts +81 -0
  15. package/src/native/linux/bunite_linux_runtime.cpp +2 -2
  16. package/src/native/mac/bunite_mac_ffi.mm +2 -2
  17. package/src/native/shared/ffi_exports.h +1 -1
  18. package/src/native/win/native_host_ffi.cpp +2 -2
  19. package/src/preload/runtime.built.js +1 -1
  20. package/src/preload/runtime.ts +54 -219
  21. package/src/preload/tsconfig.json +3 -10
  22. package/src/rpc/encrypt.ts +74 -0
  23. package/src/rpc/error.ts +58 -0
  24. package/src/rpc/framework.ts +132 -0
  25. package/src/rpc/hash.ts +142 -0
  26. package/src/rpc/index.ts +129 -0
  27. package/src/rpc/peer.ts +1055 -0
  28. package/src/rpc/renderer.ts +82 -0
  29. package/src/rpc/schema.ts +246 -0
  30. package/src/rpc/stream.ts +72 -0
  31. package/src/rpc/transport.ts +81 -0
  32. package/src/rpc/wire.ts +150 -0
  33. package/src/{preload/webviewElement.ts → webview/native.ts} +68 -48
  34. package/src/{shared/webviewPolyfill.ts → webview/polyfill.ts} +4 -7
  35. package/src/bun/core/Socket.ts +0 -187
  36. package/src/bun/core/SurfaceBrowserIPC.ts +0 -65
  37. package/src/bun/core/SurfaceManager.ts +0 -201
  38. package/src/bun/index.ts +0 -53
  39. package/src/bun/preload/index.ts +0 -73
  40. package/src/preload/tsconfig.tsbuildinfo +0 -1
  41. package/src/shared/rpc.ts +0 -424
  42. package/src/shared/rpcDemux.ts +0 -219
  43. package/src/shared/rpcWire.ts +0 -54
  44. package/src/shared/rpcWireConstants.ts +0 -3
  45. package/src/shared/webRpcHandler.ts +0 -77
  46. package/src/shared/webSocketTransport.ts +0 -26
  47. package/src/view/index.ts +0 -196
  48. /package/src/{shared → host}/cefVersion.ts +0 -0
  49. /package/src/{bun → host}/core/SurfaceRegistry.ts +0 -0
  50. /package/src/{bun → host}/core/singleInstanceLock.ts +0 -0
  51. /package/src/{bun → host}/core/windowIds.ts +0 -0
  52. /package/src/{bun → host}/events/event.ts +0 -0
  53. /package/src/{bun → host}/events/eventEmitter.ts +0 -0
  54. /package/src/{bun → host}/events/webviewEvents.ts +0 -0
  55. /package/src/{bun → host}/events/windowEvents.ts +0 -0
  56. /package/src/{shared → host}/log.ts +0 -0
  57. /package/src/{shared → host}/platform.ts +0 -0
@@ -1,11 +1,30 @@
1
1
  // <bunite-webview> custom element — registered in every appres:// page via preload.
2
2
 
3
- declare const bunite: {
4
- invoke: (method: string, params?: unknown) => Promise<any>;
5
- on: (channel: string, handler: (data: any) => void) => (() => void);
6
- off: (channel: string, handler: (data: any) => void) => void;
3
+ import type { ClientOf } from "../rpc/index";
4
+ import type { SurfaceCap } from "../rpc/framework";
5
+
6
+ declare const host: {
7
+ runtime(): Promise<ClientOf<typeof import("../rpc/framework").RuntimeCap>>;
7
8
  };
8
9
 
10
+ type SurfaceClient = ClientOf<typeof SurfaceCap>;
11
+ let _surfaceCap: Promise<SurfaceClient> | null = null;
12
+ function getSurfaceCap(): Promise<SurfaceClient> {
13
+ if (!_surfaceCap) {
14
+ _surfaceCap = host.runtime().then((r) => r.surface());
15
+ }
16
+ return _surfaceCap;
17
+ }
18
+
19
+ function callSurface<R>(fn: (s: SurfaceClient) => Promise<R> | R): Promise<R | void> {
20
+ return getSurfaceCap().then(fn).catch((err) => {
21
+ if ((globalThis as { __BUNITE_DEBUG__?: boolean }).__BUNITE_DEBUG__) {
22
+ console.warn("[bunite] surface call failed", err);
23
+ }
24
+ return undefined;
25
+ });
26
+ }
27
+
9
28
  // OverlaySyncController: ResizeObserver + rAF position polling; dirty-flag coalescing ≤1 IPC/frame.
10
29
 
11
30
  type Rect = { x: number; y: number; width: number; height: number };
@@ -105,11 +124,24 @@ class BuniteWebviewElement extends HTMLElement {
105
124
  this._aborted = false;
106
125
  this._syncHidden = false;
107
126
  this._userHidden = false;
108
- this._unsubNavigate = bunite.on("__bunite:webview.didNavigate", (data: any) => {
109
- if (data?.surfaceId === this._surfaceId) {
110
- this.dispatchEvent(new CustomEvent("did-navigate", { detail: { url: data.url } }));
127
+ const ctrl = new AbortController();
128
+ this._unsubNavigate = () => ctrl.abort();
129
+ void (async () => {
130
+ try {
131
+ const s = await getSurfaceCap();
132
+ const stream = s.didNavigate();
133
+ for await (const ev of stream) {
134
+ if (ctrl.signal.aborted) break;
135
+ if (ev.surfaceId === this._surfaceId) {
136
+ this.dispatchEvent(new CustomEvent("did-navigate", { detail: { url: ev.url } }));
137
+ }
138
+ }
139
+ } catch (err) {
140
+ if ((globalThis as { __BUNITE_DEBUG__?: boolean }).__BUNITE_DEBUG__) {
141
+ console.warn("[bunite] didNavigate stream failed", err);
142
+ }
111
143
  }
112
- });
144
+ })();
113
145
  this._waitForLayout();
114
146
  }
115
147
 
@@ -152,12 +184,10 @@ class BuniteWebviewElement extends HTMLElement {
152
184
  if (this._surfaceId != null) {
153
185
  const id = this._surfaceId;
154
186
  this._surfaceId = null;
155
- bunite.invoke("__bunite:surface.remove", { surfaceId: id }).catch(() => {});
187
+ void callSurface((s) => s.remove({ surfaceId: id }));
156
188
  } else if (this._initPromise) {
157
189
  this._initPromise
158
- .then((r) => {
159
- bunite.invoke("__bunite:surface.remove", { surfaceId: r.surfaceId }).catch(() => {});
160
- })
190
+ .then((r) => { void callSurface((s) => s.remove({ surfaceId: r.surfaceId })); })
161
191
  .catch(() => {});
162
192
  }
163
193
  this._initPromise = null;
@@ -166,10 +196,8 @@ class BuniteWebviewElement extends HTMLElement {
166
196
  attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null) {
167
197
  if (name !== "src") return;
168
198
  if (this._surfaceId != null) {
169
- bunite.invoke("__bunite:webview.navigate", {
170
- surfaceId: this._surfaceId,
171
- url: newValue || ""
172
- }).catch(() => {});
199
+ const sid = this._surfaceId;
200
+ void callSurface((s) => s.navigate({ surfaceId: sid, url: newValue || "" }));
173
201
  } else if (this._initPromise) {
174
202
  // Init in progress — queue for after completion
175
203
  this._pendingSrc = newValue || "";
@@ -185,13 +213,13 @@ class BuniteWebviewElement extends HTMLElement {
185
213
  }
186
214
 
187
215
  goBack() {
188
- if (this._surfaceId != null)
189
- bunite.invoke("__bunite:webview.goBack", { surfaceId: this._surfaceId }).catch(() => {});
216
+ const sid = this._surfaceId;
217
+ if (sid != null) void callSurface((s) => s.goBack({ surfaceId: sid }));
190
218
  }
191
219
 
192
220
  reload() {
193
- if (this._surfaceId != null)
194
- bunite.invoke("__bunite:webview.reload", { surfaceId: this._surfaceId }).catch(() => {});
221
+ const sid = this._surfaceId;
222
+ if (sid != null) void callSurface((s) => s.reload({ surfaceId: sid }));
195
223
  }
196
224
 
197
225
  navigate(url: string) {
@@ -199,11 +227,10 @@ class BuniteWebviewElement extends HTMLElement {
199
227
  }
200
228
 
201
229
  private _applySurfaceHidden() {
202
- if (this._surfaceId == null) return;
203
- bunite.invoke("__bunite:surface.setHidden", {
204
- surfaceId: this._surfaceId,
205
- hidden: this._userHidden || this._syncHidden
206
- }).catch(() => {});
230
+ const sid = this._surfaceId;
231
+ if (sid == null) return;
232
+ const hidden = this._userHidden || this._syncHidden;
233
+ void callSurface((s) => s.setHidden({ surfaceId: sid, hidden }));
207
234
  }
208
235
 
209
236
  private initSurface() {
@@ -214,43 +241,42 @@ class BuniteWebviewElement extends HTMLElement {
214
241
  const src = this._pendingSrc || this.getAttribute("src") || "";
215
242
  this._pendingSrc = null;
216
243
 
217
- const initPromise = bunite.invoke("__bunite:surface.init", {
244
+ const initPromise = getSurfaceCap().then((s) => s.init({
218
245
  src,
219
246
  x: Math.round(r.x * dpr),
220
247
  y: Math.round(r.y * dpr),
221
248
  width: Math.round(r.width * dpr),
222
249
  height: Math.round(r.height * dpr),
223
- hidden: this._userHidden
224
- }) as Promise<SurfaceInitResponse>;
250
+ hidden: this._userHidden,
251
+ })) as Promise<SurfaceInitResponse>;
225
252
  this._initPromise = initPromise;
226
253
 
227
254
  initPromise
228
255
  .then((response) => {
229
256
  if (this._initPromise !== initPromise) return;
230
257
  if (this._aborted) {
231
- bunite.invoke("__bunite:surface.remove", { surfaceId: response.surfaceId }).catch(() => {});
258
+ void callSurface((s) => s.remove({ surfaceId: response.surfaceId }));
232
259
  return;
233
260
  }
234
261
 
235
262
  this._surfaceId = response.surfaceId;
236
263
 
237
- // Apply hidden state that was set during init
238
264
  if (this._userHidden) {
239
265
  this._applySurfaceHidden();
240
266
  }
241
267
 
242
- // Apply src that was set before init completed
243
268
  if (this._pendingSrc != null) {
244
269
  const pending = this._pendingSrc;
245
270
  this._pendingSrc = null;
246
- bunite.invoke("__bunite:webview.navigate", {
247
- surfaceId: this._surfaceId,
248
- url: pending
249
- }).catch(() => {});
271
+ const sid = this._surfaceId;
272
+ if (sid != null) {
273
+ void callSurface((s) => s.navigate({ surfaceId: sid, url: pending }));
274
+ }
250
275
  }
251
276
 
252
277
  this._syncCtrl = new OverlaySyncController(this, (rect) => {
253
- if (this._surfaceId == null) return;
278
+ const sid = this._surfaceId;
279
+ if (sid == null) return;
254
280
 
255
281
  const isZero = rect.width === 0 && rect.height === 0;
256
282
  if (isZero) {
@@ -265,13 +291,9 @@ class BuniteWebviewElement extends HTMLElement {
265
291
  this._applySurfaceHidden();
266
292
  }
267
293
 
268
- bunite.invoke("__bunite:surface.resize", {
269
- surfaceId: this._surfaceId,
270
- x: rect.x,
271
- y: rect.y,
272
- w: rect.width,
273
- h: rect.height
274
- }).catch(() => {});
294
+ void callSurface((s) => s.resize({
295
+ surfaceId: sid, x: rect.x, y: rect.y, w: rect.width, h: rect.height,
296
+ }));
275
297
  });
276
298
  this._syncCtrl.start();
277
299
  })
@@ -287,16 +309,14 @@ class BuniteWebviewElement extends HTMLElement {
287
309
  if (typeof customElements !== "undefined") {
288
310
  customElements.define("bunite-webview", BuniteWebviewElement);
289
311
 
290
- // Host BrowserView HWND covers surface HWNDs on focus re-raise.
291
- const raiseAll = () => bunite.invoke("__bunite:surface.bringAllVisiblesToFront").catch(() => {});
312
+ const raiseAll = () => { void callSurface((s) => s.bringAllVisiblesToFront()); };
292
313
  document.addEventListener("pointerdown", raiseAll, true);
293
314
 
294
- // Send surfaces behind host during drag so OLE DragDrop reaches host's IDropTarget.
295
315
  document.addEventListener("dragstart", () => {
296
- bunite.invoke("__bunite:surface.setAllPassthrough", { passthrough: true }).catch(() => {});
316
+ void callSurface((s) => s.setAllPassthrough({ passthrough: true }));
297
317
  }, true);
298
318
  document.addEventListener("dragend", () => {
299
- bunite.invoke("__bunite:surface.setAllPassthrough", { passthrough: false }).catch(() => {});
319
+ void callSurface((s) => s.setAllPassthrough({ passthrough: false }));
300
320
  raiseAll();
301
321
  }, true);
302
322
  }
@@ -124,10 +124,9 @@ function definePolyfillClass(): CustomElementConstructor {
124
124
  }
125
125
 
126
126
  /**
127
- * Register the `<bunite-webview>` iframe polyfill. No-op in non-browser
128
- * environments and when the native CEF preload has already registered the element.
129
- * `BuniteView` calls this automatically on construction; call directly only when
130
- * using `<bunite-webview>` markup without instantiating `BuniteView`.
127
+ * `<bunite-webview>` iframe polyfill — `import "bunite-core/polyfill"` to register.
128
+ * No-op in non-browser environments and when the native CEF preload has already
129
+ * registered the element.
131
130
  *
132
131
  * Defaults (web fallback only — native paths bypass these):
133
132
  * - `sandbox="allow-scripts allow-forms allow-popups"` (popup-escape stays opt-in).
@@ -140,8 +139,6 @@ function definePolyfillClass(): CustomElementConstructor {
140
139
  * - `sandbox="..."` — override the default sandbox token string verbatim.
141
140
  * - `unsandboxed` — remove the sandbox attribute entirely (trusted-content escape hatch).
142
141
  */
143
- export function registerBuniteWebviewPolyfill() {
144
- if (typeof customElements === "undefined") return;
145
- if (customElements.get("bunite-webview")) return;
142
+ if (typeof customElements !== "undefined" && !customElements.get("bunite-webview")) {
146
143
  customElements.define("bunite-webview", definePolyfillClass());
147
144
  }
@@ -1,187 +0,0 @@
1
- import type { Server, ServerWebSocket } from "bun";
2
- import type { BrowserView } from "./BrowserView";
3
- import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
4
- import type { RpcPacket, RpcRequestPacket } from "../../shared/rpc";
5
- import type { GlobalIPCHandler } from "./App";
6
- import { log } from "../../shared/log";
7
- import {
8
- asUint8Array,
9
- createEncryptedRpcFrame,
10
- decodeRpcPacket,
11
- encodeRpcPacket,
12
- parseEncryptedRpcFrame
13
- } from "../../shared/rpcWire";
14
- import { RPC_AUTH_TAG_LENGTH } from "../../shared/rpcWireConstants";
15
-
16
- type ViewRegistry = {
17
- getById(id: number): BrowserView | undefined;
18
- };
19
-
20
- type WebSocketData = {
21
- webviewId: number;
22
- };
23
-
24
- let rpcServer: Server<WebSocketData> | null = null;
25
- let rpcPort = 0;
26
-
27
- const socketMap: Record<number, ServerWebSocket<WebSocketData> | null> = {};
28
- let registry: ViewRegistry | null = null;
29
- let globalIPCResolver: ((channel: string) => GlobalIPCHandler | undefined) | null = null;
30
-
31
- export function attachGlobalIPCResolver(resolver: (channel: string) => GlobalIPCHandler | undefined) {
32
- globalIPCResolver = resolver;
33
- }
34
-
35
- function encrypt(secretKey: Uint8Array, payload: Uint8Array) {
36
- const iv = new Uint8Array(randomBytes(12));
37
- const cipher = createCipheriv("aes-256-gcm", secretKey, iv);
38
- const encrypted = Buffer.concat([cipher.update(payload), cipher.final(), cipher.getAuthTag()]);
39
- return createEncryptedRpcFrame(iv, new Uint8Array(encrypted));
40
- }
41
-
42
- function decrypt(secretKey: Uint8Array, frame: Uint8Array) {
43
- const { iv, encryptedPayload } = parseEncryptedRpcFrame(frame);
44
- const ciphertext = encryptedPayload.subarray(0, encryptedPayload.byteLength - RPC_AUTH_TAG_LENGTH);
45
- const tag = encryptedPayload.subarray(encryptedPayload.byteLength - RPC_AUTH_TAG_LENGTH);
46
- const decipher = createDecipheriv("aes-256-gcm", secretKey, iv);
47
- decipher.setAuthTag(tag);
48
- return new Uint8Array(Buffer.concat([decipher.update(ciphertext), decipher.final()]));
49
- }
50
-
51
- function normalizeIncomingBinaryMessage(
52
- message: string | ArrayBuffer | Uint8Array | Buffer
53
- ): Uint8Array | null {
54
- if (typeof message === "string") {
55
- return null;
56
- }
57
- return asUint8Array(message);
58
- }
59
-
60
- export function attachBrowserViewRegistry(nextRegistry: ViewRegistry) {
61
- registry = nextRegistry;
62
- }
63
-
64
- export function ensureRpcServer() {
65
- if (rpcServer) {
66
- return { rpcServer, rpcPort };
67
- }
68
-
69
- let port = 45000;
70
- while (port <= 65535) {
71
- try {
72
- rpcServer = Bun.serve<WebSocketData>({
73
- hostname: "127.0.0.1",
74
- port,
75
- fetch(req, server) {
76
- const url = new URL(req.url);
77
- if (url.pathname !== "/socket") {
78
- return new Response("Not found", { status: 404 });
79
- }
80
-
81
- const webviewId = Number(url.searchParams.get("webviewId"));
82
- if (!Number.isFinite(webviewId)) {
83
- return new Response("Missing webviewId", { status: 400 });
84
- }
85
- if (!registry?.getById(webviewId)) {
86
- return new Response("Unknown webviewId", { status: 403 });
87
- }
88
-
89
- const upgraded = server.upgrade(req, {
90
- data: { webviewId }
91
- });
92
- return upgraded ? undefined : new Response("Upgrade failed", { status: 500 });
93
- },
94
- websocket: {
95
- open(ws) {
96
- socketMap[ws.data.webviewId] = ws;
97
- },
98
- close(ws) {
99
- socketMap[ws.data.webviewId] = null;
100
- },
101
- message(ws, message) {
102
- const view = registry?.getById(ws.data.webviewId);
103
- const binaryMessage = normalizeIncomingBinaryMessage(message);
104
- if (!view || !binaryMessage) {
105
- return;
106
- }
107
- try {
108
- const decryptedMessage = decrypt(view.secretKey, binaryMessage);
109
- const packet = decodeRpcPacket(decryptedMessage);
110
-
111
- if (packet.type === "request" && (packet as RpcRequestPacket).scope === "global") {
112
- void handleGlobalIPC(packet as RpcRequestPacket, ws.data.webviewId);
113
- return;
114
- }
115
-
116
- view.handleIncomingRpc(packet);
117
- } catch (error) {
118
- log.error("Failed to parse RPC payload", error);
119
- }
120
- }
121
- }
122
- });
123
- rpcPort = port;
124
- break;
125
- } catch (error: any) {
126
- if (error?.code === "EADDRINUSE") {
127
- port += 1;
128
- continue;
129
- }
130
- throw error;
131
- }
132
- }
133
-
134
- if (!rpcServer) {
135
- throw new Error("Could not start bunite RPC server.");
136
- }
137
-
138
- return { rpcServer, rpcPort };
139
- }
140
-
141
- export function getRpcPort(): number {
142
- return rpcPort;
143
- }
144
-
145
- async function handleGlobalIPC(packet: RpcRequestPacket, viewId: number) {
146
- const handler = globalIPCResolver?.(packet.method);
147
- if (!handler) {
148
- sendMessageToView(viewId, {
149
- type: "response",
150
- id: packet.id,
151
- success: false,
152
- error: `No handler registered for: ${packet.method}`,
153
- scope: "global"
154
- });
155
- return;
156
- }
157
- try {
158
- const result = await handler(packet.params, { viewId });
159
- sendMessageToView(viewId, {
160
- type: "response",
161
- id: packet.id,
162
- success: true,
163
- payload: result,
164
- scope: "global"
165
- });
166
- } catch (error) {
167
- sendMessageToView(viewId, {
168
- type: "response",
169
- id: packet.id,
170
- success: false,
171
- error: error instanceof Error ? error.message : String(error),
172
- scope: "global"
173
- });
174
- }
175
- }
176
-
177
- export function sendMessageToView(viewId: number, message: RpcPacket): boolean {
178
- const socket = socketMap[viewId];
179
- const view = registry?.getById(viewId);
180
- if (!socket || socket.readyState !== WebSocket.OPEN || !view) {
181
- return false;
182
- }
183
-
184
- const encrypted = encrypt(view.secretKey, encodeRpcPacket(message));
185
- socket.send(encrypted);
186
- return true;
187
- }
@@ -1,65 +0,0 @@
1
- import { getOwnedSurface } from "./SurfaceRegistry";
2
- import { sendMessageToView } from "./Socket";
3
- import { onSurfaceInit } from "./SurfaceManager";
4
-
5
- import type { GlobalIPCHandler } from "./App";
6
-
7
- // --- did-navigate forwarding ---
8
-
9
- onSurfaceInit((surfaceId, hostViewId, view) => {
10
- view.on("did-navigate", (event: any) => {
11
- sendMessageToView(hostViewId, {
12
- type: "event",
13
- channel: "__bunite:webview.didNavigate",
14
- data: { surfaceId, url: event.data.detail }
15
- });
16
- });
17
- });
18
-
19
- // --- Helpers ---
20
-
21
- function assertNum(v: unknown, label: string): number {
22
- if (typeof v !== "number" || !Number.isFinite(v)) throw new Error(`Invalid ${label}`);
23
- return v;
24
- }
25
-
26
- function assertStr(v: unknown, label: string): string {
27
- if (typeof v !== "string") throw new Error(`Invalid ${label}`);
28
- return v;
29
- }
30
-
31
- function assertObj(v: unknown, label: string): Record<string, unknown> {
32
- if (!v || typeof v !== "object") throw new Error(`Invalid ${label}`);
33
- return v as Record<string, unknown>;
34
- }
35
-
36
- // --- Handlers ---
37
-
38
- const handleGoBack: GlobalIPCHandler = async (params, ctx) => {
39
- const record = getOwnedSurface(assertNum(assertObj(params, "p").surfaceId, "surfaceId"), ctx);
40
- if (record) record.view.goBack();
41
- return {};
42
- };
43
-
44
- const handleReload: GlobalIPCHandler = async (params, ctx) => {
45
- const record = getOwnedSurface(assertNum(assertObj(params, "p").surfaceId, "surfaceId"), ctx);
46
- if (record) record.view.reload();
47
- return {};
48
- };
49
-
50
- const handleNavigate: GlobalIPCHandler = async (params, ctx) => {
51
- const p = assertObj(params, "webview.navigate params");
52
- const surfaceId = assertNum(p.surfaceId, "surfaceId");
53
- const url = assertStr(p.url, "url");
54
- const record = getOwnedSurface(surfaceId, ctx);
55
- if (record) record.view.loadURL(url);
56
- return {};
57
- };
58
-
59
- export function getWebviewIPCHandlers(): Map<string, GlobalIPCHandler> {
60
- return new Map([
61
- ["__bunite:webview.goBack", handleGoBack],
62
- ["__bunite:webview.reload", handleReload],
63
- ["__bunite:webview.navigate", handleNavigate]
64
- ]);
65
- }
@@ -1,201 +0,0 @@
1
- import { BrowserView } from "./BrowserView";
2
- import {
3
- trackSurface, untrackSurface, getOwnedSurface,
4
- getHostSurfaceIds, getSurfaceRecord,
5
- MAX_SURFACES_PER_HOST
6
- } from "./SurfaceRegistry";
7
-
8
- import type { GlobalIPCHandler } from "./App";
9
-
10
- // --- Helpers ---
11
-
12
- function applyHostOffset(hostView: BrowserView, x: number, y: number) {
13
- return { x: x + hostView.frame.x, y: y + hostView.frame.y };
14
- }
15
-
16
- function assertNum(v: unknown, label: string): number {
17
- if (typeof v !== "number" || !Number.isFinite(v)) throw new Error(`Invalid ${label}`);
18
- return v;
19
- }
20
-
21
- function assertBool(v: unknown, label: string): boolean {
22
- if (typeof v !== "boolean") throw new Error(`Invalid ${label}`);
23
- return v;
24
- }
25
-
26
- function assertStr(v: unknown, label: string): string {
27
- if (typeof v !== "string") throw new Error(`Invalid ${label}`);
28
- return v;
29
- }
30
-
31
- function assertObj(v: unknown, label: string): Record<string, unknown> {
32
- if (!v || typeof v !== "object") throw new Error(`Invalid ${label}`);
33
- return v as Record<string, unknown>;
34
- }
35
-
36
- // --- Surface lifecycle callbacks (called by SurfaceBrowserIPC after init) ---
37
-
38
- type SurfaceInitCallback = (surfaceId: number, hostViewId: number, view: BrowserView) => void;
39
- const initCallbacks: SurfaceInitCallback[] = [];
40
-
41
- export function onSurfaceInit(cb: SurfaceInitCallback) {
42
- initCallbacks.push(cb);
43
- }
44
-
45
- // --- Handlers ---
46
-
47
- const handleSurfaceInit: GlobalIPCHandler = async (params, ctx) => {
48
- const p = assertObj(params, "surface.init params");
49
- const src = assertStr(p.src, "src");
50
- const x = assertNum(p.x, "x");
51
- const y = assertNum(p.y, "y");
52
- const width = assertNum(p.width, "width");
53
- const height = assertNum(p.height, "height");
54
- const hidden = typeof p.hidden === "boolean" ? p.hidden : false;
55
-
56
- const hostView = BrowserView.getById(ctx.viewId);
57
- if (!hostView) throw new Error(`Host view not found: ${ctx.viewId}`);
58
- if (!hostView.windowId) throw new Error(`Host window not found for view: ${ctx.viewId}`);
59
-
60
- const hostIds = getHostSurfaceIds(ctx.viewId);
61
- if (hostIds && hostIds.size >= MAX_SURFACES_PER_HOST) {
62
- throw new Error(`Surface limit reached (${MAX_SURFACES_PER_HOST}) for host view ${ctx.viewId}`);
63
- }
64
-
65
- const offset = applyHostOffset(hostView, x, y);
66
- const view = new BrowserView({
67
- url: src,
68
- windowId: hostView.windowId,
69
- appresRoot: hostView.appresRoot,
70
- frame: { x: offset.x, y: offset.y, width, height },
71
- autoResize: false
72
- });
73
- trackSurface(view.id, { view, hostViewId: ctx.viewId, hidden });
74
- try {
75
- await view.whenReady();
76
- } catch {
77
- untrackSurface(view.id);
78
- view.remove();
79
- throw new Error("Surface browser creation failed or timed out");
80
- }
81
-
82
- for (const cb of initCallbacks) cb(view.id, ctx.viewId, view);
83
-
84
- if (hidden) {
85
- view.setVisible(false);
86
- } else {
87
- view.bringToFront();
88
- }
89
- return { surfaceId: view.id };
90
- };
91
-
92
- const handleSurfaceResize: GlobalIPCHandler = async (params, ctx) => {
93
- const p = assertObj(params, "surface.resize params");
94
- const surfaceId = assertNum(p.surfaceId, "surfaceId");
95
- const x = assertNum(p.x, "x");
96
- const y = assertNum(p.y, "y");
97
- const w = assertNum(p.w, "w");
98
- const h = assertNum(p.h, "h");
99
-
100
- const record = getOwnedSurface(surfaceId, ctx);
101
- if (!record) return {};
102
-
103
- const hostView = BrowserView.getById(ctx.viewId);
104
- if (hostView) {
105
- const offset = applyHostOffset(hostView, x, y);
106
- record.view.setBoundsAsync(offset.x, offset.y, w, h);
107
- }
108
- return {};
109
- };
110
-
111
- const handleSurfaceRemove: GlobalIPCHandler = async (params, ctx) => {
112
- const p = assertObj(params, "surface.remove params");
113
- const surfaceId = assertNum(p.surfaceId, "surfaceId");
114
-
115
- const record = getOwnedSurface(surfaceId, ctx);
116
- if (!record) return {};
117
-
118
- untrackSurface(surfaceId);
119
- record.view.remove();
120
- return {};
121
- };
122
-
123
- const handleSurfaceSetHidden: GlobalIPCHandler = async (params, ctx) => {
124
- const p = assertObj(params, "surface.setHidden params");
125
- const surfaceId = assertNum(p.surfaceId, "surfaceId");
126
- const hidden = assertBool(p.hidden, "hidden");
127
-
128
- const record = getOwnedSurface(surfaceId, ctx);
129
- if (!record) return {};
130
-
131
- record.hidden = hidden;
132
- record.view.setVisible(!hidden);
133
- if (!hidden) {
134
- record.view.bringToFront();
135
- }
136
- return {};
137
- };
138
-
139
- const handleSurfaceSetMasks: GlobalIPCHandler = async (params, ctx) => {
140
- const p = assertObj(params, "surface.setMasks params");
141
- const surfaceId = assertNum(p.surfaceId, "surfaceId");
142
- const masksRaw = Array.isArray(p.masks) ? p.masks : [];
143
-
144
- const record = getOwnedSurface(surfaceId, ctx);
145
- if (!record) return {};
146
-
147
- const hostView = BrowserView.getById(ctx.viewId);
148
- if (!hostView) return {};
149
-
150
- const offset = applyHostOffset(hostView, 0, 0);
151
- const rects = masksRaw.map((r: any) => ({
152
- x: assertNum(r.x, "mask.x") + offset.x,
153
- y: assertNum(r.y, "mask.y") + offset.y,
154
- w: assertNum(r.w, "mask.w"),
155
- h: assertNum(r.h, "mask.h")
156
- }));
157
-
158
- record.view.setMaskRegion(rects);
159
- return {};
160
- };
161
-
162
- const handleSurfaceSetAllPassthrough: GlobalIPCHandler = async (params, ctx) => {
163
- const p = assertObj(params, "surface.setAllPassthrough params");
164
- const passthrough = assertBool(p.passthrough, "passthrough");
165
-
166
- const ids = getHostSurfaceIds(ctx.viewId);
167
- if (!ids) return {};
168
-
169
- for (const surfaceId of ids) {
170
- const record = getSurfaceRecord(surfaceId);
171
- if (record) {
172
- record.view.setInputPassthrough(passthrough);
173
- }
174
- }
175
- return {};
176
- };
177
-
178
- const handleSurfaceBringAllVisiblesToFront: GlobalIPCHandler = async (_params, ctx) => {
179
- const ids = getHostSurfaceIds(ctx.viewId);
180
- if (!ids) return {};
181
-
182
- for (const surfaceId of ids) {
183
- const record = getSurfaceRecord(surfaceId);
184
- if (record && !record.hidden) {
185
- record.view.bringToFront();
186
- }
187
- }
188
- return {};
189
- };
190
-
191
- export function getSurfaceIPCHandlers(): Map<string, GlobalIPCHandler> {
192
- return new Map([
193
- ["__bunite:surface.init", handleSurfaceInit],
194
- ["__bunite:surface.resize", handleSurfaceResize],
195
- ["__bunite:surface.remove", handleSurfaceRemove],
196
- ["__bunite:surface.setHidden", handleSurfaceSetHidden],
197
- ["__bunite:surface.setMasks", handleSurfaceSetMasks],
198
- ["__bunite:surface.setAllPassthrough", handleSurfaceSetAllPassthrough],
199
- ["__bunite:surface.bringAllVisiblesToFront", handleSurfaceBringAllVisiblesToFront]
200
- ]);
201
- }