bunite-core 0.8.0 → 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 +9 -7
- 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
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.9.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": [
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
},
|
|
25
26
|
"optionalDependencies": {
|
|
26
27
|
"bunite-native-win-x64": "0.0.4",
|
|
27
|
-
"bunite-native-mac-arm64": "0.0.1"
|
|
28
|
+
"bunite-native-mac-arm64": "0.0.1",
|
|
29
|
+
"bunite-native-linux-x64": "0.0.1"
|
|
28
30
|
}
|
|
29
31
|
}
|
|
@@ -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_supported", 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,27 @@
|
|
|
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
|
+
type SchemaShape,
|
|
11
|
+
type ServerDescriptor,
|
|
12
|
+
} from "../../rpc/index";
|
|
13
|
+
import { createEncryptedPipe } from "../encryptedPipe";
|
|
14
|
+
import { ensureNativeRuntime, getNativeLibrary, toCString, waitForViewReady, cancelWaitForViewReady } from "../native";
|
|
15
|
+
import { attachBrowserViewRegistry, getRpcPort } from "./Socket";
|
|
16
|
+
import { getAppRuntimeOrThrow } from "./App";
|
|
8
17
|
import { randomBytes } from "node:crypto";
|
|
9
|
-
import { resolveDefaultAppResRoot } from "
|
|
18
|
+
import { resolveDefaultAppResRoot } from "../paths";
|
|
10
19
|
import { removeSurfacesForHostView } from "./SurfaceRegistry";
|
|
11
20
|
|
|
12
21
|
const BrowserViewMap: Record<number, BrowserView<any>> = {};
|
|
13
22
|
let nextWebviewId = 1;
|
|
14
23
|
|
|
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> = {
|
|
24
|
+
export type BrowserViewOptions<S extends SchemaShape = SchemaShape> = {
|
|
26
25
|
url: string | null;
|
|
27
26
|
html: string | null;
|
|
28
27
|
preload: string | null;
|
|
@@ -35,7 +34,7 @@ export type BrowserViewOptions<T = undefined> = {
|
|
|
35
34
|
width: number;
|
|
36
35
|
height: number;
|
|
37
36
|
};
|
|
38
|
-
|
|
37
|
+
serve?: ServerDescriptor<S>;
|
|
39
38
|
windowId: number;
|
|
40
39
|
autoResize: boolean;
|
|
41
40
|
navigationRules: string[] | null;
|
|
@@ -49,19 +48,14 @@ const defaultOptions: BrowserViewOptions = {
|
|
|
49
48
|
appresRoot: null,
|
|
50
49
|
preloadOrigins: undefined,
|
|
51
50
|
partition: null,
|
|
52
|
-
frame: {
|
|
53
|
-
x: 0,
|
|
54
|
-
y: 0,
|
|
55
|
-
width: 800,
|
|
56
|
-
height: 600
|
|
57
|
-
},
|
|
51
|
+
frame: { x: 0, y: 0, width: 800, height: 600 },
|
|
58
52
|
windowId: 0,
|
|
59
53
|
autoResize: true,
|
|
60
54
|
navigationRules: null,
|
|
61
55
|
sandbox: false
|
|
62
56
|
};
|
|
63
57
|
|
|
64
|
-
export class BrowserView<
|
|
58
|
+
export class BrowserView<S extends SchemaShape = SchemaShape> {
|
|
65
59
|
id = nextWebviewId++;
|
|
66
60
|
private nativeAttached = false;
|
|
67
61
|
private _readyPromise: Promise<void>;
|
|
@@ -73,20 +67,17 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
73
67
|
preloadOrigins?: string[];
|
|
74
68
|
partition: string | null;
|
|
75
69
|
frame: BrowserViewOptions["frame"];
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
private
|
|
70
|
+
readonly serveDescriptor?: ServerDescriptor<S>;
|
|
71
|
+
private connection: Connection | null = null;
|
|
72
|
+
private connectionGeneration = 0;
|
|
79
73
|
autoResize: boolean;
|
|
80
74
|
navigationRules: string[] | null;
|
|
81
75
|
sandbox: boolean;
|
|
82
76
|
secretKey: Uint8Array;
|
|
83
77
|
|
|
84
|
-
constructor(options: Partial<BrowserViewOptions<
|
|
78
|
+
constructor(options: Partial<BrowserViewOptions<S>>) {
|
|
85
79
|
ensureNativeRuntime();
|
|
86
80
|
|
|
87
|
-
this.pipe = createNativeViewPipe(this.id);
|
|
88
|
-
this.transport = this.pipe.transport;
|
|
89
|
-
|
|
90
81
|
this.windowId = options.windowId ?? defaultOptions.windowId;
|
|
91
82
|
this.url = options.url ?? defaultOptions.url;
|
|
92
83
|
this.html = options.html ?? defaultOptions.html;
|
|
@@ -95,17 +86,17 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
95
86
|
this.preloadOrigins = options.preloadOrigins ?? defaultOptions.preloadOrigins;
|
|
96
87
|
this.partition = options.partition ?? defaultOptions.partition;
|
|
97
88
|
this.frame = options.frame ?? defaultOptions.frame;
|
|
98
|
-
this.
|
|
89
|
+
this.serveDescriptor = options.serve;
|
|
99
90
|
this.autoResize = options.autoResize ?? defaultOptions.autoResize;
|
|
100
91
|
this.navigationRules = options.navigationRules ?? defaultOptions.navigationRules;
|
|
101
92
|
this.sandbox = options.sandbox ?? defaultOptions.sandbox;
|
|
102
93
|
this.secretKey = new Uint8Array(randomBytes(32));
|
|
103
94
|
|
|
104
95
|
if (this.sandbox) {
|
|
105
|
-
throw new Error("sandboxed BrowserView is not implemented
|
|
96
|
+
throw new Error("sandboxed BrowserView is not implemented yet.");
|
|
106
97
|
}
|
|
107
98
|
if (this.partition) {
|
|
108
|
-
log.warn("BrowserView.partition is not implemented
|
|
99
|
+
log.warn("BrowserView.partition is not implemented yet.");
|
|
109
100
|
}
|
|
110
101
|
|
|
111
102
|
const preloadScript = buildViewPreloadScript({
|
|
@@ -117,8 +108,6 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
117
108
|
});
|
|
118
109
|
|
|
119
110
|
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
111
|
this._readyPromise = waitForViewReady(this.id);
|
|
123
112
|
this.nativeAttached =
|
|
124
113
|
getNativeLibrary()?.symbols.bunite_view_create(
|
|
@@ -139,8 +128,6 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
139
128
|
) ?? false;
|
|
140
129
|
|
|
141
130
|
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
131
|
this.on("did-navigate", (event: any) => {
|
|
145
132
|
this.url = event.data?.detail ?? this.url;
|
|
146
133
|
removeSurfacesForHostView(this.id);
|
|
@@ -148,7 +135,7 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
148
135
|
} else {
|
|
149
136
|
cancelWaitForViewReady(this.id);
|
|
150
137
|
this._readyPromise = Promise.reject(new Error("Native view creation failed"));
|
|
151
|
-
this._readyPromise.catch(() => {});
|
|
138
|
+
this._readyPromise.catch(() => {});
|
|
152
139
|
}
|
|
153
140
|
}
|
|
154
141
|
|
|
@@ -169,19 +156,44 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
169
156
|
return Object.values(BrowserViewMap);
|
|
170
157
|
}
|
|
171
158
|
|
|
172
|
-
|
|
173
|
-
this.
|
|
159
|
+
async attachNewConnection(pipe: BytesPipe): Promise<void> {
|
|
160
|
+
this.connectionGeneration += 1;
|
|
161
|
+
const myGen = this.connectionGeneration;
|
|
162
|
+
if (this.connection) {
|
|
163
|
+
try { (this.connection as { transport?: { close?(): void } }).transport?.close?.(); } catch { /* swallow */ }
|
|
164
|
+
this.connection = null;
|
|
165
|
+
}
|
|
166
|
+
const encPipe = await createEncryptedPipe(pipe, this.secretKey);
|
|
167
|
+
if (myGen !== this.connectionGeneration) {
|
|
168
|
+
try { encPipe.close(); } catch { /* swallow */ }
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const runtime = getAppRuntimeOrThrow().createViewRuntime(this.id);
|
|
172
|
+
this.connection = createConnection({
|
|
173
|
+
transport: createFrameTransport(encPipe),
|
|
174
|
+
mode: "native",
|
|
175
|
+
origin: "appres://app.internal",
|
|
176
|
+
runtime,
|
|
177
|
+
});
|
|
178
|
+
if (this.serveDescriptor) {
|
|
179
|
+
this.connection.serve(this.serveDescriptor);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
detachNewConnection(): void {
|
|
184
|
+
this.connectionGeneration += 1;
|
|
185
|
+
if (this.connection) {
|
|
186
|
+
try { (this.connection as { transport?: { close?(): void } }).transport?.close?.(); } catch { /* swallow */ }
|
|
187
|
+
this.connection = null;
|
|
188
|
+
}
|
|
174
189
|
}
|
|
175
190
|
|
|
176
|
-
get
|
|
177
|
-
return
|
|
191
|
+
get rpcConnection(): Connection | null {
|
|
192
|
+
return this.connection;
|
|
178
193
|
}
|
|
179
194
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (this.nativeAttached) {
|
|
183
|
-
getNativeLibrary()?.symbols.bunite_view_set_anchor(this.id, modeInt, inset);
|
|
184
|
-
}
|
|
195
|
+
get rpcPort() {
|
|
196
|
+
return getRpcPort();
|
|
185
197
|
}
|
|
186
198
|
|
|
187
199
|
executeJavaScript(script: string) {
|
|
@@ -245,7 +257,6 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
245
257
|
}
|
|
246
258
|
}
|
|
247
259
|
|
|
248
|
-
/** Fire-and-forget setBounds — does not block on the UI thread. */
|
|
249
260
|
setBoundsAsync(x: number, y: number, width: number, height: number) {
|
|
250
261
|
this.frame = { x, y, width, height };
|
|
251
262
|
if (this.nativeAttached) {
|
|
@@ -297,11 +308,7 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
297
308
|
cancelWaitForViewReady(this.id);
|
|
298
309
|
this.nativeAttached = false;
|
|
299
310
|
for (const eventName of [
|
|
300
|
-
"will-navigate",
|
|
301
|
-
"did-navigate",
|
|
302
|
-
"dom-ready",
|
|
303
|
-
"new-window-open",
|
|
304
|
-
"permission-requested"
|
|
311
|
+
"will-navigate", "did-navigate", "dom-ready", "new-window-open", "permission-requested"
|
|
305
312
|
]) {
|
|
306
313
|
buniteEventEmitter.removeAllListeners(`${eventName}-${this.id}`);
|
|
307
314
|
}
|
|
@@ -309,12 +316,7 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
309
316
|
}
|
|
310
317
|
|
|
311
318
|
on(
|
|
312
|
-
name:
|
|
313
|
-
| "will-navigate"
|
|
314
|
-
| "did-navigate"
|
|
315
|
-
| "dom-ready"
|
|
316
|
-
| "new-window-open"
|
|
317
|
-
| "permission-requested",
|
|
319
|
+
name: "will-navigate" | "did-navigate" | "dom-ready" | "new-window-open" | "permission-requested",
|
|
318
320
|
handler: (event: unknown) => void
|
|
319
321
|
) {
|
|
320
322
|
const specificName = `${name}-${this.id}`;
|
|
@@ -324,7 +326,5 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
324
326
|
}
|
|
325
327
|
|
|
326
328
|
attachBrowserViewRegistry({
|
|
327
|
-
getById(id) {
|
|
328
|
-
return BrowserView.getById(id);
|
|
329
|
-
}
|
|
329
|
+
getById(id) { return BrowserView.getById(id); }
|
|
330
330
|
});
|
|
@@ -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 { SchemaShape, ServerDescriptor } 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<S extends SchemaShape = SchemaShape> = {
|
|
11
11
|
title: string;
|
|
12
12
|
frame: {
|
|
13
13
|
x: number;
|
|
@@ -22,7 +22,7 @@ export type WindowOptionsType<T = undefined> = {
|
|
|
22
22
|
preload: string | null;
|
|
23
23
|
appresRoot: string | null;
|
|
24
24
|
preloadOrigins?: string[];
|
|
25
|
-
|
|
25
|
+
serve?: ServerDescriptor<S>;
|
|
26
26
|
titleBarStyle: "hidden" | "hiddenInset" | "default";
|
|
27
27
|
transparent: boolean;
|
|
28
28
|
hidden?: boolean;
|
|
@@ -58,7 +58,7 @@ export function getLastFocusedWindowId(): number | null {
|
|
|
58
58
|
return lastFocusedWindowId;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
export class BrowserWindow<
|
|
61
|
+
export class BrowserWindow<S extends SchemaShape = SchemaShape> {
|
|
62
62
|
id = getNextWindowId();
|
|
63
63
|
private nativeAttached = false;
|
|
64
64
|
title: string;
|
|
@@ -127,7 +127,7 @@ export class BrowserWindow<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
127
127
|
buniteEventEmitter.removeAllListeners(`close-requested-${this.id}`);
|
|
128
128
|
};
|
|
129
129
|
|
|
130
|
-
constructor(options: Partial<WindowOptionsType<
|
|
130
|
+
constructor(options: Partial<WindowOptionsType<S>> = {}) {
|
|
131
131
|
ensureNativeRuntime();
|
|
132
132
|
|
|
133
133
|
this.title = options.title ?? defaultOptions.title;
|
|
@@ -181,7 +181,7 @@ export class BrowserWindow<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
181
181
|
buniteEventEmitter.on(`resize-${this.id}`, this.handleNativeResize);
|
|
182
182
|
buniteEventEmitter.on(`close-${this.id}`, this.handleNativeClose);
|
|
183
183
|
|
|
184
|
-
const webview = new BrowserView({
|
|
184
|
+
const webview = new BrowserView<S>({
|
|
185
185
|
url: this.url,
|
|
186
186
|
html: this.html,
|
|
187
187
|
preload: this.preload,
|
|
@@ -193,7 +193,7 @@ export class BrowserWindow<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
193
193
|
width: this.frame.width,
|
|
194
194
|
height: this.frame.height
|
|
195
195
|
},
|
|
196
|
-
|
|
196
|
+
serve: options.serve,
|
|
197
197
|
windowId: this.id,
|
|
198
198
|
navigationRules: this.navigationRules,
|
|
199
199
|
sandbox: this.sandbox
|
|
@@ -202,10 +202,10 @@ export class BrowserWindow<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
202
202
|
this.webviewId = webview.id;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
get view(): BrowserView<
|
|
205
|
+
get view(): BrowserView<S> {
|
|
206
206
|
const view = BrowserView.getById(this.webviewId);
|
|
207
207
|
if (!view) throw new Error(`BrowserWindow ${this.id} has no attached view`);
|
|
208
|
-
return view as BrowserView<
|
|
208
|
+
return view as BrowserView<S>;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
static getById(id: number) {
|
|
@@ -216,8 +216,8 @@ export class BrowserWindow<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
216
216
|
return Object.values(BrowserWindowMap);
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
get webview() {
|
|
220
|
-
return BrowserView.getById(this.webviewId) as BrowserView<
|
|
219
|
+
get webview(): BrowserView<S> | undefined {
|
|
220
|
+
return BrowserView.getById(this.webviewId) as BrowserView<S> | undefined;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
show() {
|