bunite-core 0.8.1 → 0.10.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 +71 -65
- package/src/{bun → host}/core/BrowserWindow.ts +15 -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 +105 -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 +52 -219
- package/src/preload/tsconfig.json +3 -10
- package/src/rpc/encrypt.ts +74 -0
- package/src/rpc/error.ts +68 -0
- package/src/rpc/framework.ts +132 -0
- package/src/rpc/index.ts +138 -0
- package/src/rpc/peer.ts +1438 -0
- package/src/rpc/renderer.ts +80 -0
- package/src/rpc/schema.ts +229 -0
- package/src/rpc/stream.ts +72 -0
- package/src/rpc/transport.ts +81 -0
- package/src/rpc/wire.ts +164 -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
package/package.json
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bunite-core",
|
|
3
3
|
"description": "Uniting UI and Bun",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.10.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"setup:cef": "bun ../tools/bunite-dev/scripts/setup-cef.ts",
|
|
8
8
|
"build:native:win": "cmake -S . -B build/win -DBUNITE_TARGET_ARCH=x64 && cmake --build build/win --config Release",
|
|
9
9
|
"build:native:linux": "cmake -S . -B build/linux -DCMAKE_BUILD_TYPE=Release && cmake --build build/linux",
|
|
10
|
-
"build:preload": "bun build src/preload/runtime.ts --outfile src/preload/runtime.built.js --target browser --minify"
|
|
10
|
+
"build:preload": "bun build src/preload/runtime.ts --outfile src/preload/runtime.built.js --target browser --minify",
|
|
11
|
+
"prepublishOnly": "bun run build:preload && rm -f src/preload/tsconfig.tsbuildinfo"
|
|
11
12
|
},
|
|
12
13
|
"exports": {
|
|
13
|
-
".": "./src/
|
|
14
|
-
"./
|
|
15
|
-
"./
|
|
16
|
-
"./
|
|
14
|
+
".": "./src/host/index.ts",
|
|
15
|
+
"./rpc": "./src/rpc/index.ts",
|
|
16
|
+
"./rpc/renderer": "./src/rpc/renderer.ts",
|
|
17
|
+
"./polyfill": "./src/webview/polyfill.ts",
|
|
17
18
|
"./package.json": "./package.json"
|
|
18
19
|
},
|
|
19
20
|
"files": [
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { isAbsolute, join, resolve } from "node:path";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
-
import { getBaseDir } from "
|
|
4
|
-
import { BuniteEvent } from "../events/event";
|
|
3
|
+
import { getBaseDir } from "../paths";
|
|
5
4
|
import { buniteEventEmitter } from "../events/eventEmitter";
|
|
6
5
|
import {
|
|
7
6
|
getNativeEngineName,
|
|
@@ -13,14 +12,15 @@ import {
|
|
|
13
12
|
setNativeLogLevel,
|
|
14
13
|
toCString,
|
|
15
14
|
type NativeBootstrapOptions
|
|
16
|
-
} from "../
|
|
17
|
-
import {
|
|
15
|
+
} from "../native";
|
|
16
|
+
import { ensureRpcServer } from "./Socket";
|
|
18
17
|
import { BrowserWindow } from "./BrowserWindow";
|
|
19
|
-
import {
|
|
20
|
-
import
|
|
21
|
-
import { log, logLevelToInt } from "
|
|
18
|
+
import { createSurfaceCapImpl } from "./SurfaceManager";
|
|
19
|
+
import "./SurfaceBrowserIPC";
|
|
20
|
+
import { log, logLevelToInt } from "../log";
|
|
21
|
+
import { RuntimeCap, SurfaceCap, IpcError, type ImplOf } from "../../rpc/index";
|
|
22
22
|
|
|
23
|
-
import type { LogLevel } from "
|
|
23
|
+
import type { LogLevel } from "../log";
|
|
24
24
|
|
|
25
25
|
type AppOptions = NativeBootstrapOptions & {
|
|
26
26
|
userDataDir?: string;
|
|
@@ -28,15 +28,17 @@ type AppOptions = NativeBootstrapOptions & {
|
|
|
28
28
|
logLevel?: LogLevel;
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
let _instance: AppRuntime | null = null;
|
|
32
|
+
export function getAppRuntimeOrThrow(): AppRuntime {
|
|
33
|
+
if (!_instance) throw new Error("AppRuntime not yet instantiated");
|
|
34
|
+
return _instance;
|
|
35
|
+
}
|
|
32
36
|
|
|
33
37
|
function normalizeAppResPath(path: string): string {
|
|
34
38
|
return path.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
export class AppRuntime {
|
|
38
|
-
private stubKeepAliveTimer: ReturnType<typeof setInterval> | null = null;
|
|
39
|
-
private readonly globalIPCHandlers = new Map<string, GlobalIPCHandler>();
|
|
40
42
|
private exitOnLastWindowClosed = true;
|
|
41
43
|
private quitting = false;
|
|
42
44
|
private pumpActive = false;
|
|
@@ -44,7 +46,11 @@ export class AppRuntime {
|
|
|
44
46
|
readonly ready: Promise<void>;
|
|
45
47
|
|
|
46
48
|
constructor(options: AppOptions = {}) {
|
|
49
|
+
if (_instance) throw new Error("AppRuntime already instantiated");
|
|
50
|
+
_instance = this;
|
|
51
|
+
ensureRpcServer();
|
|
47
52
|
this.ready = this.bootstrap(options);
|
|
53
|
+
this.ready.catch(() => { if (_instance === this) _instance = null; });
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
private async bootstrap(options: AppOptions) {
|
|
@@ -82,55 +88,31 @@ export class AppRuntime {
|
|
|
82
88
|
process.env.BUNITE_USER_DATA_DIR = join(appDataDir, name);
|
|
83
89
|
}
|
|
84
90
|
|
|
85
|
-
|
|
86
|
-
allowStub: options.allowStub,
|
|
91
|
+
await initNativeRuntime({
|
|
87
92
|
hideConsole: options.hideConsole,
|
|
88
93
|
popupBlocking: options.popupBlocking,
|
|
89
94
|
engineFlags: options.engineFlags
|
|
90
95
|
});
|
|
91
96
|
|
|
92
|
-
if (options.logLevel
|
|
97
|
+
if (options.logLevel) {
|
|
93
98
|
setNativeLogLevel(logLevelToInt(options.logLevel));
|
|
94
99
|
}
|
|
95
100
|
|
|
96
|
-
attachGlobalIPCResolver((channel) => this.getGlobalIPCHandler(channel));
|
|
97
|
-
|
|
98
|
-
for (const [channel, handler] of getSurfaceIPCHandlers()) {
|
|
99
|
-
this.globalIPCHandlers.set(channel, handler);
|
|
100
|
-
}
|
|
101
|
-
for (const [channel, handler] of getWebviewIPCHandlers()) {
|
|
102
|
-
this.globalIPCHandlers.set(channel, handler);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
101
|
setRouteRequestHandler((requestId, path) => this.handleRouteRequest(requestId, path));
|
|
106
102
|
|
|
107
103
|
for (const path of this.appresHandlers.keys()) {
|
|
108
104
|
getNativeLibrary()?.symbols.bunite_register_appres_route(toCString(path));
|
|
109
105
|
}
|
|
110
106
|
|
|
111
|
-
if (this.exitOnLastWindowClosed
|
|
107
|
+
if (this.exitOnLastWindowClosed) {
|
|
112
108
|
buniteEventEmitter.on("all-windows-closed", () => {
|
|
113
|
-
if (this.quitting)
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
109
|
+
if (this.quitting) return;
|
|
116
110
|
queueMicrotask(() => {
|
|
117
|
-
if (this.quitting)
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
if (BrowserWindow.getAll().length === 0) {
|
|
121
|
-
this.quit();
|
|
122
|
-
}
|
|
111
|
+
if (this.quitting) return;
|
|
112
|
+
if (BrowserWindow.getAll().length === 0) this.quit();
|
|
123
113
|
});
|
|
124
114
|
});
|
|
125
115
|
}
|
|
126
|
-
|
|
127
|
-
ensureRpcServer();
|
|
128
|
-
buniteEventEmitter.emitEvent(
|
|
129
|
-
new BuniteEvent("ready", {
|
|
130
|
-
usingStub: runtime.usingStub,
|
|
131
|
-
artifacts: runtime.artifacts
|
|
132
|
-
})
|
|
133
|
-
);
|
|
134
116
|
}
|
|
135
117
|
|
|
136
118
|
on(name: string, handler: (payload: unknown) => void) {
|
|
@@ -144,20 +126,11 @@ export class AppRuntime {
|
|
|
144
126
|
}
|
|
145
127
|
|
|
146
128
|
run() {
|
|
147
|
-
const runtime = getNativeRuntimeState();
|
|
148
|
-
if (!runtime?.nativeLoaded) {
|
|
149
|
-
if (!this.stubKeepAliveTimer) {
|
|
150
|
-
log.warn("Running without a native event loop. Keeping the process alive in stub mode.");
|
|
151
|
-
this.stubKeepAliveTimer = setInterval(() => {}, 60_000);
|
|
152
|
-
}
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
129
|
const lib = getNativeLibrary();
|
|
157
130
|
lib?.symbols.bunite_run_loop();
|
|
158
131
|
|
|
159
132
|
if (process.platform === "darwin" || process.platform === "linux") {
|
|
160
|
-
//
|
|
133
|
+
// mac/linux: cooperative pump on Bun's main thread (NSApp/GTK first-thread constraint).
|
|
161
134
|
this.pumpActive = true;
|
|
162
135
|
const pump = () => {
|
|
163
136
|
if (!this.pumpActive) return;
|
|
@@ -165,16 +138,13 @@ export class AppRuntime {
|
|
|
165
138
|
setImmediate(pump);
|
|
166
139
|
};
|
|
167
140
|
pump();
|
|
168
|
-
} else if (!this.stubKeepAliveTimer) {
|
|
169
|
-
// Engines with a dedicated UI thread (Windows CEF) only need Bun's loop kept alive.
|
|
170
|
-
this.stubKeepAliveTimer = setInterval(() => {}, 60_000);
|
|
171
141
|
}
|
|
142
|
+
// Windows: native UI thread is separate. Bun's loop stays alive via the RPC
|
|
143
|
+
// listening socket started by ensureRpcServer() in the constructor.
|
|
172
144
|
}
|
|
173
145
|
|
|
174
146
|
quit(code = 0) {
|
|
175
|
-
if (this.quitting)
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
147
|
+
if (this.quitting) return;
|
|
178
148
|
this.quitting = true;
|
|
179
149
|
|
|
180
150
|
const event = buniteEventEmitter.events.app.beforeQuit({});
|
|
@@ -184,33 +154,29 @@ export class AppRuntime {
|
|
|
184
154
|
return;
|
|
185
155
|
}
|
|
186
156
|
this.pumpActive = false;
|
|
187
|
-
if (this.stubKeepAliveTimer) {
|
|
188
|
-
clearInterval(this.stubKeepAliveTimer);
|
|
189
|
-
this.stubKeepAliveTimer = null;
|
|
190
|
-
}
|
|
191
157
|
getNativeLibrary()?.symbols.bunite_quit();
|
|
158
|
+
if (_instance === this) _instance = null;
|
|
192
159
|
process.exitCode = code;
|
|
193
160
|
process.exit(code);
|
|
194
161
|
}
|
|
195
162
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
throw new
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
return this.globalIPCHandlers.get(channel);
|
|
163
|
+
createViewRuntime(viewId: number): ImplOf<typeof RuntimeCap> {
|
|
164
|
+
const notImpl = (name: string) => {
|
|
165
|
+
throw new IpcError({ code: "not_found", message: `Runtime.${name}` });
|
|
166
|
+
};
|
|
167
|
+
const impl = {
|
|
168
|
+
window: () => notImpl("window"),
|
|
169
|
+
dialogs: () => notImpl("dialogs"),
|
|
170
|
+
clipboard: () => notImpl("clipboard"),
|
|
171
|
+
shell: () => notImpl("shell"),
|
|
172
|
+
appName: () => "bunite-app",
|
|
173
|
+
appVersion: () => this.version,
|
|
174
|
+
theme: (): "light" | "dark" => "light",
|
|
175
|
+
themeWatch: () => notImpl("themeWatch"),
|
|
176
|
+
surface: (_: void, ctx: Parameters<ImplOf<typeof RuntimeCap>["surface"]>[1]) =>
|
|
177
|
+
ctx.exportCap(SurfaceCap, createSurfaceCapImpl(viewId)),
|
|
178
|
+
} satisfies ImplOf<typeof RuntimeCap>;
|
|
179
|
+
return impl;
|
|
214
180
|
}
|
|
215
181
|
|
|
216
182
|
private readonly appresHandlers = new Map<string, () => string>();
|
|
@@ -262,14 +228,12 @@ export class AppRuntime {
|
|
|
262
228
|
private cachedEngineName: string | null | undefined;
|
|
263
229
|
private cachedEngineVersion: string | null | undefined;
|
|
264
230
|
|
|
265
|
-
/** Active engine identifier reported by the native adapter (e.g. `"cef"`, `"wkwebview"`, `"webkitgtk"`). */
|
|
266
231
|
get engineName(): string | null {
|
|
267
232
|
if (this.cachedEngineName !== undefined) return this.cachedEngineName;
|
|
268
233
|
this.cachedEngineName = getNativeEngineName();
|
|
269
234
|
return this.cachedEngineName;
|
|
270
235
|
}
|
|
271
236
|
|
|
272
|
-
/** Engine version string reported by the native adapter. Format depends on engine. */
|
|
273
237
|
get engineVersion(): string | null {
|
|
274
238
|
if (this.cachedEngineVersion !== undefined) return this.cachedEngineVersion;
|
|
275
239
|
this.cachedEngineVersion = getNativeEngineVersion();
|
|
@@ -1,28 +1,25 @@
|
|
|
1
1
|
import { ptr } from "bun:ffi";
|
|
2
|
-
import { buildViewPreloadScript } from "../
|
|
3
|
-
import { log } from "
|
|
2
|
+
import { buildViewPreloadScript } from "../preloadBundle";
|
|
3
|
+
import { log } from "../log";
|
|
4
4
|
import { buniteEventEmitter } from "../events/eventEmitter";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import {
|
|
6
|
+
createConnection,
|
|
7
|
+
createFrameTransport,
|
|
8
|
+
type Connection,
|
|
9
|
+
type BytesPipe,
|
|
10
|
+
} from "../../rpc/index";
|
|
11
|
+
import { createEncryptedPipe } from "../encryptedPipe";
|
|
12
|
+
import { ensureNativeRuntime, getNativeLibrary, toCString, waitForViewReady, cancelWaitForViewReady } from "../native";
|
|
13
|
+
import { attachBrowserViewRegistry, getRpcPort } from "./Socket";
|
|
14
|
+
import { getAppRuntimeOrThrow } from "./App";
|
|
8
15
|
import { randomBytes } from "node:crypto";
|
|
9
|
-
import { resolveDefaultAppResRoot } from "
|
|
16
|
+
import { resolveDefaultAppResRoot } from "../paths";
|
|
10
17
|
import { removeSurfacesForHostView } from "./SurfaceRegistry";
|
|
11
18
|
|
|
12
|
-
const BrowserViewMap: Record<number, BrowserView
|
|
19
|
+
const BrowserViewMap: Record<number, BrowserView> = {};
|
|
13
20
|
let nextWebviewId = 1;
|
|
14
21
|
|
|
15
|
-
|
|
16
|
-
let handler: ((packet: RpcPacket) => void) | undefined;
|
|
17
|
-
const transport: RpcTransport = {
|
|
18
|
-
send: (packet) => { sendMessageToView(viewId, packet); },
|
|
19
|
-
registerHandler: (h) => { handler = h; },
|
|
20
|
-
unregisterHandler: () => { handler = undefined; }
|
|
21
|
-
};
|
|
22
|
-
return { transport, receive: (packet: RpcPacket) => handler?.(packet) };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export type BrowserViewOptions<T = undefined> = {
|
|
22
|
+
export type BrowserViewOptions = {
|
|
26
23
|
url: string | null;
|
|
27
24
|
html: string | null;
|
|
28
25
|
preload: string | null;
|
|
@@ -35,7 +32,8 @@ export type BrowserViewOptions<T = undefined> = {
|
|
|
35
32
|
width: number;
|
|
36
33
|
height: number;
|
|
37
34
|
};
|
|
38
|
-
|
|
35
|
+
/** Setup callback fired when a renderer connection attaches. Use `conn.serve(cap, impl)` or `conn.serveAll(schema, impls)`. */
|
|
36
|
+
serve?: (conn: Connection) => void;
|
|
39
37
|
windowId: number;
|
|
40
38
|
autoResize: boolean;
|
|
41
39
|
navigationRules: string[] | null;
|
|
@@ -49,19 +47,14 @@ const defaultOptions: BrowserViewOptions = {
|
|
|
49
47
|
appresRoot: null,
|
|
50
48
|
preloadOrigins: undefined,
|
|
51
49
|
partition: null,
|
|
52
|
-
frame: {
|
|
53
|
-
x: 0,
|
|
54
|
-
y: 0,
|
|
55
|
-
width: 800,
|
|
56
|
-
height: 600
|
|
57
|
-
},
|
|
50
|
+
frame: { x: 0, y: 0, width: 800, height: 600 },
|
|
58
51
|
windowId: 0,
|
|
59
52
|
autoResize: true,
|
|
60
53
|
navigationRules: null,
|
|
61
54
|
sandbox: false
|
|
62
55
|
};
|
|
63
56
|
|
|
64
|
-
export class BrowserView
|
|
57
|
+
export class BrowserView {
|
|
65
58
|
id = nextWebviewId++;
|
|
66
59
|
private nativeAttached = false;
|
|
67
60
|
private _readyPromise: Promise<void>;
|
|
@@ -73,20 +66,17 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
73
66
|
preloadOrigins?: string[];
|
|
74
67
|
partition: string | null;
|
|
75
68
|
frame: BrowserViewOptions["frame"];
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
private
|
|
69
|
+
readonly serveSetup?: (conn: Connection) => void;
|
|
70
|
+
private connection: Connection | null = null;
|
|
71
|
+
private connectionGeneration = 0;
|
|
79
72
|
autoResize: boolean;
|
|
80
73
|
navigationRules: string[] | null;
|
|
81
74
|
sandbox: boolean;
|
|
82
75
|
secretKey: Uint8Array;
|
|
83
76
|
|
|
84
|
-
constructor(options: Partial<BrowserViewOptions
|
|
77
|
+
constructor(options: Partial<BrowserViewOptions>) {
|
|
85
78
|
ensureNativeRuntime();
|
|
86
79
|
|
|
87
|
-
this.pipe = createNativeViewPipe(this.id);
|
|
88
|
-
this.transport = this.pipe.transport;
|
|
89
|
-
|
|
90
80
|
this.windowId = options.windowId ?? defaultOptions.windowId;
|
|
91
81
|
this.url = options.url ?? defaultOptions.url;
|
|
92
82
|
this.html = options.html ?? defaultOptions.html;
|
|
@@ -95,17 +85,17 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
95
85
|
this.preloadOrigins = options.preloadOrigins ?? defaultOptions.preloadOrigins;
|
|
96
86
|
this.partition = options.partition ?? defaultOptions.partition;
|
|
97
87
|
this.frame = options.frame ?? defaultOptions.frame;
|
|
98
|
-
this.
|
|
88
|
+
this.serveSetup = options.serve;
|
|
99
89
|
this.autoResize = options.autoResize ?? defaultOptions.autoResize;
|
|
100
90
|
this.navigationRules = options.navigationRules ?? defaultOptions.navigationRules;
|
|
101
91
|
this.sandbox = options.sandbox ?? defaultOptions.sandbox;
|
|
102
92
|
this.secretKey = new Uint8Array(randomBytes(32));
|
|
103
93
|
|
|
104
94
|
if (this.sandbox) {
|
|
105
|
-
throw new Error("sandboxed BrowserView is not implemented
|
|
95
|
+
throw new Error("sandboxed BrowserView is not implemented yet.");
|
|
106
96
|
}
|
|
107
97
|
if (this.partition) {
|
|
108
|
-
log.warn("BrowserView.partition is not implemented
|
|
98
|
+
log.warn("BrowserView.partition is not implemented yet.");
|
|
109
99
|
}
|
|
110
100
|
|
|
111
101
|
const preloadScript = buildViewPreloadScript({
|
|
@@ -117,8 +107,6 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
117
107
|
});
|
|
118
108
|
|
|
119
109
|
BrowserViewMap[this.id] = this;
|
|
120
|
-
this.rpc?.setTransport(this.transport);
|
|
121
|
-
// Register before native create — view-ready can fire on the UI thread before bunite_view_create returns.
|
|
122
110
|
this._readyPromise = waitForViewReady(this.id);
|
|
123
111
|
this.nativeAttached =
|
|
124
112
|
getNativeLibrary()?.symbols.bunite_view_create(
|
|
@@ -139,8 +127,6 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
139
127
|
) ?? false;
|
|
140
128
|
|
|
141
129
|
if (this.nativeAttached) {
|
|
142
|
-
// did-navigate (not will-): nav destroys JS context without disconnectedCallback;
|
|
143
|
-
// will-navigate fires even when rules deny → would leak surfaces.
|
|
144
130
|
this.on("did-navigate", (event: any) => {
|
|
145
131
|
this.url = event.data?.detail ?? this.url;
|
|
146
132
|
removeSurfacesForHostView(this.id);
|
|
@@ -148,7 +134,7 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
148
134
|
} else {
|
|
149
135
|
cancelWaitForViewReady(this.id);
|
|
150
136
|
this._readyPromise = Promise.reject(new Error("Native view creation failed"));
|
|
151
|
-
this._readyPromise.catch(() => {});
|
|
137
|
+
this._readyPromise.catch(() => {});
|
|
152
138
|
}
|
|
153
139
|
}
|
|
154
140
|
|
|
@@ -169,19 +155,51 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
169
155
|
return Object.values(BrowserViewMap);
|
|
170
156
|
}
|
|
171
157
|
|
|
172
|
-
|
|
173
|
-
this.
|
|
158
|
+
async attachNewConnection(pipe: BytesPipe): Promise<void> {
|
|
159
|
+
this.connectionGeneration += 1;
|
|
160
|
+
const myGen = this.connectionGeneration;
|
|
161
|
+
if (this.connection) {
|
|
162
|
+
try { (this.connection as { transport?: { close?(): void } }).transport?.close?.(); } catch { /* swallow */ }
|
|
163
|
+
this.connection = null;
|
|
164
|
+
}
|
|
165
|
+
const encPipe = await createEncryptedPipe(pipe, this.secretKey);
|
|
166
|
+
if (myGen !== this.connectionGeneration) {
|
|
167
|
+
try { encPipe.close(); } catch { /* swallow */ }
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const runtime = getAppRuntimeOrThrow().createViewRuntime(this.id);
|
|
171
|
+
this.connection = createConnection({
|
|
172
|
+
transport: createFrameTransport(encPipe),
|
|
173
|
+
mode: "native",
|
|
174
|
+
origin: "appres://app.internal",
|
|
175
|
+
runtime,
|
|
176
|
+
attestation: {
|
|
177
|
+
origin: "appres://app.internal",
|
|
178
|
+
topOrigin: "appres://app.internal",
|
|
179
|
+
partition: this.partition ?? "default",
|
|
180
|
+
isAppRes: true,
|
|
181
|
+
isMainFrame: true,
|
|
182
|
+
userGesture: false,
|
|
183
|
+
level: "app-internal",
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
this.serveSetup?.(this.connection);
|
|
174
187
|
}
|
|
175
188
|
|
|
176
|
-
|
|
177
|
-
|
|
189
|
+
detachNewConnection(): void {
|
|
190
|
+
this.connectionGeneration += 1;
|
|
191
|
+
if (this.connection) {
|
|
192
|
+
try { (this.connection as { transport?: { close?(): void } }).transport?.close?.(); } catch { /* swallow */ }
|
|
193
|
+
this.connection = null;
|
|
194
|
+
}
|
|
178
195
|
}
|
|
179
196
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
197
|
+
get rpcConnection(): Connection | null {
|
|
198
|
+
return this.connection;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
get rpcPort() {
|
|
202
|
+
return getRpcPort();
|
|
185
203
|
}
|
|
186
204
|
|
|
187
205
|
executeJavaScript(script: string) {
|
|
@@ -245,7 +263,6 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
245
263
|
}
|
|
246
264
|
}
|
|
247
265
|
|
|
248
|
-
/** Fire-and-forget setBounds — does not block on the UI thread. */
|
|
249
266
|
setBoundsAsync(x: number, y: number, width: number, height: number) {
|
|
250
267
|
this.frame = { x, y, width, height };
|
|
251
268
|
if (this.nativeAttached) {
|
|
@@ -297,11 +314,7 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
297
314
|
cancelWaitForViewReady(this.id);
|
|
298
315
|
this.nativeAttached = false;
|
|
299
316
|
for (const eventName of [
|
|
300
|
-
"will-navigate",
|
|
301
|
-
"did-navigate",
|
|
302
|
-
"dom-ready",
|
|
303
|
-
"new-window-open",
|
|
304
|
-
"permission-requested"
|
|
317
|
+
"will-navigate", "did-navigate", "dom-ready", "new-window-open", "permission-requested"
|
|
305
318
|
]) {
|
|
306
319
|
buniteEventEmitter.removeAllListeners(`${eventName}-${this.id}`);
|
|
307
320
|
}
|
|
@@ -309,12 +322,7 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
309
322
|
}
|
|
310
323
|
|
|
311
324
|
on(
|
|
312
|
-
name:
|
|
313
|
-
| "will-navigate"
|
|
314
|
-
| "did-navigate"
|
|
315
|
-
| "dom-ready"
|
|
316
|
-
| "new-window-open"
|
|
317
|
-
| "permission-requested",
|
|
325
|
+
name: "will-navigate" | "did-navigate" | "dom-ready" | "new-window-open" | "permission-requested",
|
|
318
326
|
handler: (event: unknown) => void
|
|
319
327
|
) {
|
|
320
328
|
const specificName = `${name}-${this.id}`;
|
|
@@ -324,7 +332,5 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
324
332
|
}
|
|
325
333
|
|
|
326
334
|
attachBrowserViewRegistry({
|
|
327
|
-
getById(id) {
|
|
328
|
-
return BrowserView.getById(id);
|
|
329
|
-
}
|
|
335
|
+
getById(id) { return BrowserView.getById(id); }
|
|
330
336
|
});
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
2
2
|
import { BuniteEvent } from "../events/event";
|
|
3
3
|
import { buniteEventEmitter } from "../events/eventEmitter";
|
|
4
|
-
import { ensureNativeRuntime, getNativeLibrary, toCString } from "../
|
|
5
|
-
import { BrowserView
|
|
6
|
-
import type {
|
|
4
|
+
import { ensureNativeRuntime, getNativeLibrary, toCString } from "../native";
|
|
5
|
+
import { BrowserView } from "./BrowserView";
|
|
6
|
+
import type { Connection } from "../../rpc/index";
|
|
7
7
|
import { getNextWindowId } from "./windowIds";
|
|
8
|
-
import { getBaseDir, resolveDefaultAppResRoot } from "
|
|
8
|
+
import { getBaseDir, resolveDefaultAppResRoot } from "../paths";
|
|
9
9
|
|
|
10
|
-
export type WindowOptionsType
|
|
10
|
+
export type WindowOptionsType = {
|
|
11
11
|
title: string;
|
|
12
12
|
frame: {
|
|
13
13
|
x: number;
|
|
@@ -22,7 +22,8 @@ export type WindowOptionsType<T = undefined> = {
|
|
|
22
22
|
preload: string | null;
|
|
23
23
|
appresRoot: string | null;
|
|
24
24
|
preloadOrigins?: string[];
|
|
25
|
-
|
|
25
|
+
/** Setup callback fired when the window's renderer connection attaches. */
|
|
26
|
+
serve?: (conn: Connection) => void;
|
|
26
27
|
titleBarStyle: "hidden" | "hiddenInset" | "default";
|
|
27
28
|
transparent: boolean;
|
|
28
29
|
hidden?: boolean;
|
|
@@ -50,7 +51,7 @@ const defaultOptions: WindowOptionsType = {
|
|
|
50
51
|
sandbox: false
|
|
51
52
|
};
|
|
52
53
|
|
|
53
|
-
const BrowserWindowMap: Record<number, BrowserWindow
|
|
54
|
+
const BrowserWindowMap: Record<number, BrowserWindow> = {};
|
|
54
55
|
|
|
55
56
|
let lastFocusedWindowId: number | null = null;
|
|
56
57
|
|
|
@@ -58,7 +59,7 @@ export function getLastFocusedWindowId(): number | null {
|
|
|
58
59
|
return lastFocusedWindowId;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
export class BrowserWindow
|
|
62
|
+
export class BrowserWindow {
|
|
62
63
|
id = getNextWindowId();
|
|
63
64
|
private nativeAttached = false;
|
|
64
65
|
title: string;
|
|
@@ -127,7 +128,7 @@ export class BrowserWindow<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
127
128
|
buniteEventEmitter.removeAllListeners(`close-requested-${this.id}`);
|
|
128
129
|
};
|
|
129
130
|
|
|
130
|
-
constructor(options: Partial<WindowOptionsType
|
|
131
|
+
constructor(options: Partial<WindowOptionsType> = {}) {
|
|
131
132
|
ensureNativeRuntime();
|
|
132
133
|
|
|
133
134
|
this.title = options.title ?? defaultOptions.title;
|
|
@@ -193,7 +194,7 @@ export class BrowserWindow<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
193
194
|
width: this.frame.width,
|
|
194
195
|
height: this.frame.height
|
|
195
196
|
},
|
|
196
|
-
|
|
197
|
+
serve: options.serve,
|
|
197
198
|
windowId: this.id,
|
|
198
199
|
navigationRules: this.navigationRules,
|
|
199
200
|
sandbox: this.sandbox
|
|
@@ -202,10 +203,10 @@ export class BrowserWindow<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
202
203
|
this.webviewId = webview.id;
|
|
203
204
|
}
|
|
204
205
|
|
|
205
|
-
get view(): BrowserView
|
|
206
|
+
get view(): BrowserView {
|
|
206
207
|
const view = BrowserView.getById(this.webviewId);
|
|
207
208
|
if (!view) throw new Error(`BrowserWindow ${this.id} has no attached view`);
|
|
208
|
-
return view as BrowserView
|
|
209
|
+
return view as BrowserView;
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
static getById(id: number) {
|
|
@@ -216,8 +217,8 @@ export class BrowserWindow<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
216
217
|
return Object.values(BrowserWindowMap);
|
|
217
218
|
}
|
|
218
219
|
|
|
219
|
-
get webview() {
|
|
220
|
-
return BrowserView.getById(this.webviewId) as BrowserView
|
|
220
|
+
get webview(): BrowserView | undefined {
|
|
221
|
+
return BrowserView.getById(this.webviewId) as BrowserView | undefined;
|
|
221
222
|
}
|
|
222
223
|
|
|
223
224
|
show() {
|