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.
- package/package.json +7 -6
- package/src/{bun → host}/core/App.ts +45 -81
- package/src/{bun → host}/core/BrowserView.ts +64 -64
- package/src/{bun → host}/core/BrowserWindow.ts +14 -14
- package/src/host/core/Socket.ts +98 -0
- package/src/host/core/SurfaceBrowserIPC.ts +7 -0
- package/src/host/core/SurfaceManager.ts +154 -0
- package/src/host/encryptedPipe.ts +62 -0
- package/src/{bun → host}/events/appEvents.ts +0 -1
- package/src/host/index.ts +29 -0
- package/src/{bun/proc → host}/native.ts +38 -52
- package/src/{shared → host}/paths.ts +20 -26
- package/src/{bun/preload/inline.ts → host/preloadBundle.ts} +2 -2
- package/src/host/serveWeb.ts +81 -0
- package/src/native/linux/bunite_linux_runtime.cpp +2 -2
- package/src/native/mac/bunite_mac_ffi.mm +2 -2
- package/src/native/shared/ffi_exports.h +1 -1
- package/src/native/win/native_host_ffi.cpp +2 -2
- package/src/preload/runtime.built.js +1 -1
- package/src/preload/runtime.ts +54 -219
- package/src/preload/tsconfig.json +3 -10
- package/src/rpc/encrypt.ts +74 -0
- package/src/rpc/error.ts +58 -0
- package/src/rpc/framework.ts +132 -0
- package/src/rpc/hash.ts +142 -0
- package/src/rpc/index.ts +129 -0
- package/src/rpc/peer.ts +1055 -0
- package/src/rpc/renderer.ts +82 -0
- package/src/rpc/schema.ts +246 -0
- package/src/rpc/stream.ts +72 -0
- package/src/rpc/transport.ts +81 -0
- package/src/rpc/wire.ts +150 -0
- package/src/{preload/webviewElement.ts → webview/native.ts} +68 -48
- package/src/{shared/webviewPolyfill.ts → webview/polyfill.ts} +4 -7
- package/src/bun/core/Socket.ts +0 -187
- package/src/bun/core/SurfaceBrowserIPC.ts +0 -65
- package/src/bun/core/SurfaceManager.ts +0 -201
- package/src/bun/index.ts +0 -53
- package/src/bun/preload/index.ts +0 -73
- package/src/preload/tsconfig.tsbuildinfo +0 -1
- package/src/shared/rpc.ts +0 -424
- package/src/shared/rpcDemux.ts +0 -219
- package/src/shared/rpcWire.ts +0 -54
- package/src/shared/rpcWireConstants.ts +0 -3
- package/src/shared/webRpcHandler.ts +0 -77
- package/src/shared/webSocketTransport.ts +0 -26
- package/src/view/index.ts +0 -196
- /package/src/{shared → host}/cefVersion.ts +0 -0
- /package/src/{bun → host}/core/SurfaceRegistry.ts +0 -0
- /package/src/{bun → host}/core/singleInstanceLock.ts +0 -0
- /package/src/{bun → host}/core/windowIds.ts +0 -0
- /package/src/{bun → host}/events/event.ts +0 -0
- /package/src/{bun → host}/events/eventEmitter.ts +0 -0
- /package/src/{bun → host}/events/webviewEvents.ts +0 -0
- /package/src/{bun → host}/events/windowEvents.ts +0 -0
- /package/src/{shared → host}/log.ts +0 -0
- /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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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
|
-
|
|
189
|
-
|
|
216
|
+
const sid = this._surfaceId;
|
|
217
|
+
if (sid != null) void callSurface((s) => s.goBack({ surfaceId: sid }));
|
|
190
218
|
}
|
|
191
219
|
|
|
192
220
|
reload() {
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
url: pending
|
|
249
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
269
|
-
surfaceId:
|
|
270
|
-
|
|
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
|
-
|
|
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
|
-
|
|
316
|
+
void callSurface((s) => s.setAllPassthrough({ passthrough: true }));
|
|
297
317
|
}, true);
|
|
298
318
|
document.addEventListener("dragend", () => {
|
|
299
|
-
|
|
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
|
-
*
|
|
128
|
-
* environments and when the native CEF preload has already
|
|
129
|
-
*
|
|
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
|
-
|
|
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
|
}
|
package/src/bun/core/Socket.ts
DELETED
|
@@ -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
|
-
}
|