bunite-core 0.5.0 → 0.8.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 +4 -2
- package/src/bun/core/App.ts +35 -34
- package/src/bun/core/BrowserView.ts +3 -9
- package/src/bun/core/singleInstanceLock.ts +91 -0
- package/src/bun/index.ts +5 -6
- package/src/bun/preload/inline.ts +1 -2
- package/src/bun/proc/native.ts +54 -78
- package/src/native/linux/bunite_linux_appres.cpp +173 -0
- package/src/native/linux/bunite_linux_ffi.cpp +263 -0
- package/src/native/linux/bunite_linux_internal.h +148 -0
- package/src/native/linux/bunite_linux_preload.cpp +1 -0
- package/src/native/linux/bunite_linux_runtime.cpp +120 -0
- package/src/native/linux/bunite_linux_utils.cpp +114 -0
- package/src/native/linux/bunite_linux_view.cpp +244 -0
- package/src/native/linux/bunite_linux_window.cpp +101 -0
- package/src/native/mac/bunite_mac_appres.mm +163 -0
- package/src/native/mac/bunite_mac_ffi.mm +470 -0
- package/src/native/mac/bunite_mac_internal.h +151 -0
- package/src/native/mac/bunite_mac_runtime.mm +15 -0
- package/src/native/mac/bunite_mac_utils.mm +121 -0
- package/src/native/mac/bunite_mac_view.mm +279 -0
- package/src/native/mac/bunite_mac_window.mm +187 -0
- package/src/native/shared/ffi_exports.h +13 -13
- package/src/native/shared/permissions.h +14 -0
- package/src/native/shared/webview_storage.h +6 -3
- package/src/native/win/native_host_cef.cpp +4 -8
- package/src/native/win/native_host_ffi.cpp +76 -123
- package/src/native/win/native_host_internal.h +5 -3
- package/src/native/win/native_host_runtime.cpp +2 -6
- package/src/native/win/native_host_utils.cpp +23 -52
- package/src/native/win/process_helper_win.cpp +1 -3
- package/src/preload/runtime.ts +1 -3
- package/src/preload/tsconfig.tsbuildinfo +1 -1
- package/src/preload/webviewElement.ts +3 -8
- package/src/shared/paths.ts +35 -44
- package/src/shared/platform.ts +1 -2
- package/src/shared/rpc.ts +4 -13
- package/src/shared/rpcDemux.ts +47 -2
- package/src/shared/webRpcHandler.ts +1 -3
- package/src/shared/webviewPolyfill.ts +64 -6
- package/src/bun/core/Utils.ts +0 -301
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bunite-core",
|
|
3
3
|
"description": "Uniting UI and Bun",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.8.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
|
+
"build:native:linux": "cmake -S . -B build/linux -DCMAKE_BUILD_TYPE=Release && cmake --build build/linux",
|
|
9
10
|
"build:preload": "bun build src/preload/runtime.ts --outfile src/preload/runtime.built.js --target browser --minify"
|
|
10
11
|
},
|
|
11
12
|
"exports": {
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
"msgpackr": "^1.11.9"
|
|
23
24
|
},
|
|
24
25
|
"optionalDependencies": {
|
|
25
|
-
"bunite-native-win-x64": "0.0.
|
|
26
|
+
"bunite-native-win-x64": "0.0.4",
|
|
27
|
+
"bunite-native-mac-arm64": "0.0.1"
|
|
26
28
|
}
|
|
27
29
|
}
|
package/src/bun/core/App.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { isAbsolute, join, resolve } from "node:path";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { getBaseDir } from "../../shared/paths";
|
|
4
|
-
import { dlopen, FFIType } from "bun:ffi";
|
|
5
4
|
import { BuniteEvent } from "../events/event";
|
|
6
5
|
import { buniteEventEmitter } from "../events/eventEmitter";
|
|
7
|
-
import { handleMessageBoxResponse } from "./Utils";
|
|
8
6
|
import {
|
|
7
|
+
getNativeEngineName,
|
|
8
|
+
getNativeEngineVersion,
|
|
9
9
|
getNativeLibrary,
|
|
10
10
|
initNativeRuntime,
|
|
11
11
|
getNativeRuntimeState,
|
|
@@ -24,7 +24,6 @@ import type { LogLevel } from "../../shared/log";
|
|
|
24
24
|
|
|
25
25
|
type AppOptions = NativeBootstrapOptions & {
|
|
26
26
|
userDataDir?: string;
|
|
27
|
-
cefDir?: string;
|
|
28
27
|
exitOnLastWindowClosed?: boolean;
|
|
29
28
|
logLevel?: LogLevel;
|
|
30
29
|
};
|
|
@@ -40,6 +39,7 @@ export class AppRuntime {
|
|
|
40
39
|
private readonly globalIPCHandlers = new Map<string, GlobalIPCHandler>();
|
|
41
40
|
private exitOnLastWindowClosed = true;
|
|
42
41
|
private quitting = false;
|
|
42
|
+
private pumpActive = false;
|
|
43
43
|
|
|
44
44
|
readonly ready: Promise<void>;
|
|
45
45
|
|
|
@@ -56,10 +56,6 @@ export class AppRuntime {
|
|
|
56
56
|
log.setLevel(options.logLevel);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
if (options.cefDir) {
|
|
60
|
-
process.env.BUNITE_CEF_DIR = options.cefDir;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
59
|
if (options.userDataDir) {
|
|
64
60
|
process.env.BUNITE_USER_DATA_DIR = options.userDataDir;
|
|
65
61
|
} else if (!process.env.BUNITE_USER_DATA_DIR) {
|
|
@@ -90,7 +86,7 @@ export class AppRuntime {
|
|
|
90
86
|
allowStub: options.allowStub,
|
|
91
87
|
hideConsole: options.hideConsole,
|
|
92
88
|
popupBlocking: options.popupBlocking,
|
|
93
|
-
|
|
89
|
+
engineFlags: options.engineFlags
|
|
94
90
|
});
|
|
95
91
|
|
|
96
92
|
if (options.logLevel && runtime.nativeLoaded) {
|
|
@@ -106,12 +102,6 @@ export class AppRuntime {
|
|
|
106
102
|
this.globalIPCHandlers.set(channel, handler);
|
|
107
103
|
}
|
|
108
104
|
|
|
109
|
-
this.globalIPCHandlers.set("__bunite:messageBoxResponse", (params) => {
|
|
110
|
-
const { requestId, response } = params as { requestId: number; response: number };
|
|
111
|
-
handleMessageBoxResponse(requestId, response);
|
|
112
|
-
return {};
|
|
113
|
-
});
|
|
114
|
-
|
|
115
105
|
setRouteRequestHandler((requestId, path) => this.handleRouteRequest(requestId, path));
|
|
116
106
|
|
|
117
107
|
for (const path of this.appresHandlers.keys()) {
|
|
@@ -155,16 +145,28 @@ export class AppRuntime {
|
|
|
155
145
|
|
|
156
146
|
run() {
|
|
157
147
|
const runtime = getNativeRuntimeState();
|
|
158
|
-
if (runtime?.nativeLoaded) {
|
|
159
|
-
getNativeLibrary()?.symbols.bunite_run_loop();
|
|
148
|
+
if (!runtime?.nativeLoaded) {
|
|
160
149
|
if (!this.stubKeepAliveTimer) {
|
|
150
|
+
log.warn("Running without a native event loop. Keeping the process alive in stub mode.");
|
|
161
151
|
this.stubKeepAliveTimer = setInterval(() => {}, 60_000);
|
|
162
152
|
}
|
|
163
153
|
return;
|
|
164
154
|
}
|
|
165
155
|
|
|
166
|
-
|
|
167
|
-
|
|
156
|
+
const lib = getNativeLibrary();
|
|
157
|
+
lib?.symbols.bunite_run_loop();
|
|
158
|
+
|
|
159
|
+
if (process.platform === "darwin" || process.platform === "linux") {
|
|
160
|
+
// AppKit / WebKitGTK share Bun's main thread — step-drive via setImmediate (no blocking loop).
|
|
161
|
+
this.pumpActive = true;
|
|
162
|
+
const pump = () => {
|
|
163
|
+
if (!this.pumpActive) return;
|
|
164
|
+
lib?.symbols.bunite_pump_once();
|
|
165
|
+
setImmediate(pump);
|
|
166
|
+
};
|
|
167
|
+
pump();
|
|
168
|
+
} else if (!this.stubKeepAliveTimer) {
|
|
169
|
+
// Engines with a dedicated UI thread (Windows CEF) only need Bun's loop kept alive.
|
|
168
170
|
this.stubKeepAliveTimer = setInterval(() => {}, 60_000);
|
|
169
171
|
}
|
|
170
172
|
}
|
|
@@ -181,6 +183,7 @@ export class AppRuntime {
|
|
|
181
183
|
this.quitting = false;
|
|
182
184
|
return;
|
|
183
185
|
}
|
|
186
|
+
this.pumpActive = false;
|
|
184
187
|
if (this.stubKeepAliveTimer) {
|
|
185
188
|
clearInterval(this.stubKeepAliveTimer);
|
|
186
189
|
this.stubKeepAliveTimer = null;
|
|
@@ -256,22 +259,20 @@ export class AppRuntime {
|
|
|
256
259
|
}
|
|
257
260
|
}
|
|
258
261
|
|
|
259
|
-
private
|
|
262
|
+
private cachedEngineName: string | null | undefined;
|
|
263
|
+
private cachedEngineVersion: string | null | undefined;
|
|
260
264
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
this.
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
this.cachedCefVersion = `${v(0)}.${v(1)}.${v(2)}+chromium-${v(4)}.${v(5)}.${v(6)}.${v(7)}`;
|
|
274
|
-
} catch { /* leave as null */ }
|
|
275
|
-
return this.cachedCefVersion;
|
|
265
|
+
/** Active engine identifier reported by the native adapter (e.g. `"cef"`, `"wkwebview"`, `"webkitgtk"`). */
|
|
266
|
+
get engineName(): string | null {
|
|
267
|
+
if (this.cachedEngineName !== undefined) return this.cachedEngineName;
|
|
268
|
+
this.cachedEngineName = getNativeEngineName();
|
|
269
|
+
return this.cachedEngineName;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Engine version string reported by the native adapter. Format depends on engine. */
|
|
273
|
+
get engineVersion(): string | null {
|
|
274
|
+
if (this.cachedEngineVersion !== undefined) return this.cachedEngineVersion;
|
|
275
|
+
this.cachedEngineVersion = getNativeEngineVersion();
|
|
276
|
+
return this.cachedEngineVersion;
|
|
276
277
|
}
|
|
277
278
|
}
|
|
@@ -8,7 +8,6 @@ import { attachBrowserViewRegistry, getRpcPort, sendMessageToView } from "./Sock
|
|
|
8
8
|
import { randomBytes } from "node:crypto";
|
|
9
9
|
import { resolveDefaultAppResRoot } from "../../shared/paths";
|
|
10
10
|
import { removeSurfacesForHostView } from "./SurfaceRegistry";
|
|
11
|
-
import { cancelPendingMessageBoxesForView } from "./Utils";
|
|
12
11
|
|
|
13
12
|
const BrowserViewMap: Record<number, BrowserView<any>> = {};
|
|
14
13
|
let nextWebviewId = 1;
|
|
@@ -119,8 +118,7 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
119
118
|
|
|
120
119
|
BrowserViewMap[this.id] = this;
|
|
121
120
|
this.rpc?.setTransport(this.transport);
|
|
122
|
-
// Register
|
|
123
|
-
// on the CEF UI thread before bunite_view_create returns to JS.
|
|
121
|
+
// Register before native create — view-ready can fire on the UI thread before bunite_view_create returns.
|
|
124
122
|
this._readyPromise = waitForViewReady(this.id);
|
|
125
123
|
this.nativeAttached =
|
|
126
124
|
getNativeLibrary()?.symbols.bunite_view_create(
|
|
@@ -141,13 +139,10 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
141
139
|
) ?? false;
|
|
142
140
|
|
|
143
141
|
if (this.nativeAttached) {
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
// Uses did-navigate (not will-navigate) because will-navigate fires even
|
|
147
|
-
// when navigation is denied by navigationRules.
|
|
142
|
+
// did-navigate (not will-): nav destroys JS context without disconnectedCallback;
|
|
143
|
+
// will-navigate fires even when rules deny → would leak surfaces.
|
|
148
144
|
this.on("did-navigate", (event: any) => {
|
|
149
145
|
this.url = event.data?.detail ?? this.url;
|
|
150
|
-
cancelPendingMessageBoxesForView(this.id);
|
|
151
146
|
removeSurfacesForHostView(this.id);
|
|
152
147
|
});
|
|
153
148
|
} else {
|
|
@@ -298,7 +293,6 @@ export class BrowserView<T extends RpcWithTransport = RpcWithTransport> {
|
|
|
298
293
|
}
|
|
299
294
|
|
|
300
295
|
detachFromNative() {
|
|
301
|
-
cancelPendingMessageBoxesForView(this.id);
|
|
302
296
|
removeSurfacesForHostView(this.id);
|
|
303
297
|
cancelWaitForViewReady(this.id);
|
|
304
298
|
this.nativeAttached = false;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { closeSync, openSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
export type SingleInstanceLock =
|
|
7
|
+
| { acquired: true; release(): void }
|
|
8
|
+
| { acquired: false; holderPid?: number };
|
|
9
|
+
|
|
10
|
+
export function lockPathFor(key: string): string {
|
|
11
|
+
const slug = createHash("sha1").update(key).digest("hex").slice(0, 16);
|
|
12
|
+
return join(tmpdir(), `bunite-instance-${slug}.lock`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isProcessAlive(pid: number): boolean {
|
|
16
|
+
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
17
|
+
try {
|
|
18
|
+
process.kill(pid, 0);
|
|
19
|
+
return true;
|
|
20
|
+
} catch (e) {
|
|
21
|
+
const code = (e as NodeJS.ErrnoException).code;
|
|
22
|
+
// EPERM means the process exists but is owned by someone else.
|
|
23
|
+
return code === "EPERM";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readHolderPid(path: string): number | undefined {
|
|
28
|
+
try {
|
|
29
|
+
const pid = parseInt(readFileSync(path, "utf8").trim(), 10);
|
|
30
|
+
return Number.isFinite(pid) && pid > 0 ? pid : undefined;
|
|
31
|
+
} catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Acquire a process-wide single-instance lock keyed by `key`. Returns a release
|
|
38
|
+
* handle on success, or the current holder's PID on contention. Dead-PID locks
|
|
39
|
+
* are reclaimed automatically; in-flight contention (lockfile present but PID
|
|
40
|
+
* not yet written / unreadable) is surfaced as `acquired: false` without a PID
|
|
41
|
+
* rather than racing to unlink the contender's lockfile.
|
|
42
|
+
*
|
|
43
|
+
* Use BEFORE app.init() — second instance can prompt UX and exit instead of
|
|
44
|
+
* crashing on engine-specific userDataDir locks.
|
|
45
|
+
*
|
|
46
|
+
* Caveats: `process.on("exit")` cleanup does not run on SIGKILL or crash; the
|
|
47
|
+
* next instance reclaims via PID-liveness probe. PID reuse can mask a dead
|
|
48
|
+
* original holder until the unrelated process exits.
|
|
49
|
+
*/
|
|
50
|
+
export function acquireSingleInstanceLock(key: string): SingleInstanceLock {
|
|
51
|
+
const path = lockPathFor(key);
|
|
52
|
+
|
|
53
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
54
|
+
try {
|
|
55
|
+
const fd = openSync(path, "wx");
|
|
56
|
+
writeFileSync(fd, String(process.pid));
|
|
57
|
+
closeSync(fd);
|
|
58
|
+
let released = false;
|
|
59
|
+
const release = () => {
|
|
60
|
+
if (released) return;
|
|
61
|
+
released = true;
|
|
62
|
+
// Verify ownership before unlinking — guards against deleting a lockfile
|
|
63
|
+
// that has been reclaimed by another process if our exit was delayed.
|
|
64
|
+
if (readHolderPid(path) === process.pid) {
|
|
65
|
+
try { unlinkSync(path); } catch { /* already gone */ }
|
|
66
|
+
}
|
|
67
|
+
process.off("exit", release);
|
|
68
|
+
};
|
|
69
|
+
process.on("exit", release);
|
|
70
|
+
return { acquired: true, release };
|
|
71
|
+
} catch (e) {
|
|
72
|
+
const code = (e as NodeJS.ErrnoException).code;
|
|
73
|
+
if (code !== "EEXIST") throw e;
|
|
74
|
+
|
|
75
|
+
const holderPid = readHolderPid(path);
|
|
76
|
+
if (holderPid === undefined) {
|
|
77
|
+
// Lockfile present but PID not written yet (contender between
|
|
78
|
+
// openSync and writeFileSync) or content corrupt. Don't race to unlink.
|
|
79
|
+
return { acquired: false };
|
|
80
|
+
}
|
|
81
|
+
if (isProcessAlive(holderPid)) {
|
|
82
|
+
return { acquired: false, holderPid };
|
|
83
|
+
}
|
|
84
|
+
// Stale: holder is dead. Drop and retry once.
|
|
85
|
+
try { unlinkSync(path); } catch { /* lost the race; loop retries */ }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Second attempt also lost the race — surface as contention without a PID.
|
|
90
|
+
return { acquired: false };
|
|
91
|
+
}
|
package/src/bun/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { AppRuntime } from "./core/App";
|
|
2
2
|
import { BrowserWindow, type WindowOptionsType } from "./core/BrowserWindow";
|
|
3
3
|
import { BrowserView, type BrowserViewOptions } from "./core/BrowserView";
|
|
4
|
-
import * as Utils from "./core/Utils";
|
|
5
4
|
import { buniteEventEmitter } from "./events/eventEmitter";
|
|
6
5
|
import { BuniteEvent } from "./events/event";
|
|
7
6
|
import { completePermissionRequest } from "./proc/native";
|
|
@@ -13,17 +12,17 @@ import {
|
|
|
13
12
|
type RpcSchema,
|
|
14
13
|
type RpcWithTransport
|
|
15
14
|
} from "../shared/rpc";
|
|
16
|
-
import { createRpcTransportDemuxer, type RpcChannelHandle, type RpcTransportDemuxer, type RpcTransportDemuxerOptions } from "../shared/rpcDemux";
|
|
15
|
+
import { createRpcTransportDemuxer, type RpcChannelHandle, type RpcDemuxBufferPolicy, type RpcTransportDemuxer, type RpcTransportDemuxerOptions } from "../shared/rpcDemux";
|
|
17
16
|
import { createWebSocketTransport, type WebSocketLike, type WebSocketTransportPipe } from "../shared/webSocketTransport";
|
|
18
17
|
import { createWebRpcHandler, type WebRpcClient } from "../shared/webRpcHandler";
|
|
19
|
-
import
|
|
18
|
+
import { acquireSingleInstanceLock, type SingleInstanceLock } from "./core/singleInstanceLock";
|
|
20
19
|
import { log, type LogLevel } from "../shared/log";
|
|
21
20
|
|
|
22
21
|
export {
|
|
22
|
+
acquireSingleInstanceLock,
|
|
23
23
|
AppRuntime,
|
|
24
24
|
BrowserWindow,
|
|
25
25
|
BrowserView,
|
|
26
|
-
Utils,
|
|
27
26
|
buniteEventEmitter,
|
|
28
27
|
completePermissionRequest,
|
|
29
28
|
createRpc,
|
|
@@ -41,12 +40,12 @@ export type {
|
|
|
41
40
|
BuniteRpcSchema,
|
|
42
41
|
BrowserViewOptions,
|
|
43
42
|
RpcChannelHandle,
|
|
44
|
-
|
|
45
|
-
MessageBoxResponse,
|
|
43
|
+
RpcDemuxBufferPolicy,
|
|
46
44
|
RpcSchema,
|
|
47
45
|
RpcWithTransport,
|
|
48
46
|
RpcTransportDemuxer,
|
|
49
47
|
RpcTransportDemuxerOptions,
|
|
48
|
+
SingleInstanceLock,
|
|
50
49
|
WebRpcClient,
|
|
51
50
|
WebSocketLike,
|
|
52
51
|
WebSocketTransportPipe,
|
|
@@ -53,8 +53,7 @@ function readCustomPreload(preload: string | null, appresRoot: string | null) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
//
|
|
57
|
-
// Embedded at bundle time so bun --compile includes it without filesystem access.
|
|
56
|
+
// Bundled at build time so bun --compile works without filesystem access.
|
|
58
57
|
// @ts-ignore — text import attribute
|
|
59
58
|
import embeddedPreloadRuntime from "../../preload/runtime.built.js" with { type: "text" };
|
|
60
59
|
|
package/src/bun/proc/native.ts
CHANGED
|
@@ -9,7 +9,12 @@ export type NativeBootstrapOptions = {
|
|
|
9
9
|
allowStub?: boolean;
|
|
10
10
|
hideConsole?: boolean;
|
|
11
11
|
popupBlocking?: boolean;
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Engine-specific opaque config. Each adapter parses its own keys.
|
|
14
|
+
* - CEF (Windows): Chromium command-line flags as `Record<flag, value | true>`.
|
|
15
|
+
* - WKWebView, WebKitGTK: defined per adapter; refer to the adapter's bootstrap docs.
|
|
16
|
+
*/
|
|
17
|
+
engineFlags?: Record<string, string | boolean>;
|
|
13
18
|
};
|
|
14
19
|
|
|
15
20
|
export type NativeRuntimeState = {
|
|
@@ -23,15 +28,17 @@ type CStringPointer = Pointer;
|
|
|
23
28
|
|
|
24
29
|
type NativeSymbols = {
|
|
25
30
|
bunite_abi_version: () => number;
|
|
31
|
+
bunite_engine_name: () => CString;
|
|
32
|
+
bunite_engine_version: () => CString;
|
|
26
33
|
bunite_set_log_level: (level: number) => void;
|
|
27
34
|
bunite_init: (
|
|
28
|
-
|
|
29
|
-
cefDir: CStringPointer,
|
|
35
|
+
engineDir: CStringPointer,
|
|
30
36
|
hideConsole: boolean,
|
|
31
37
|
popupBlocking: boolean,
|
|
32
|
-
|
|
38
|
+
engineConfigJson: CStringPointer
|
|
33
39
|
) => boolean;
|
|
34
40
|
bunite_run_loop: () => void;
|
|
41
|
+
bunite_pump_once: () => void;
|
|
35
42
|
bunite_quit: () => void;
|
|
36
43
|
bunite_free_cstring: (value: Pointer) => void;
|
|
37
44
|
bunite_window_create: (
|
|
@@ -101,16 +108,6 @@ type NativeSymbols = {
|
|
|
101
108
|
bunite_view_close_devtools: (viewId: number) => void;
|
|
102
109
|
bunite_view_toggle_devtools: (viewId: number) => void;
|
|
103
110
|
bunite_complete_permission_request: (requestId: number, state: number) => void;
|
|
104
|
-
bunite_show_message_box: (
|
|
105
|
-
windowId: number,
|
|
106
|
-
type: CStringPointer,
|
|
107
|
-
title: CStringPointer,
|
|
108
|
-
message: CStringPointer,
|
|
109
|
-
detail: CStringPointer,
|
|
110
|
-
buttons: CStringPointer,
|
|
111
|
-
defaultId: number,
|
|
112
|
-
cancelId: number
|
|
113
|
-
) => number;
|
|
114
111
|
bunite_set_webview_event_handler: (handler: JSCallback) => void;
|
|
115
112
|
bunite_set_window_event_handler: (handler: JSCallback) => void;
|
|
116
113
|
};
|
|
@@ -119,26 +116,35 @@ type LoadedNativeLibrary = {
|
|
|
119
116
|
symbols: NativeSymbols;
|
|
120
117
|
};
|
|
121
118
|
|
|
122
|
-
const messageBoxButtonSeparator = "\x1f";
|
|
123
|
-
const unsetCancelId = -1;
|
|
124
|
-
|
|
125
119
|
const nativeSymbolDefinitions = {
|
|
126
120
|
bunite_abi_version: {
|
|
127
121
|
args: [],
|
|
128
122
|
returns: FFIType.i32
|
|
129
123
|
},
|
|
124
|
+
bunite_engine_name: {
|
|
125
|
+
args: [],
|
|
126
|
+
returns: FFIType.cstring
|
|
127
|
+
},
|
|
128
|
+
bunite_engine_version: {
|
|
129
|
+
args: [],
|
|
130
|
+
returns: FFIType.cstring
|
|
131
|
+
},
|
|
130
132
|
bunite_set_log_level: {
|
|
131
133
|
args: [FFIType.i32],
|
|
132
134
|
returns: FFIType.void
|
|
133
135
|
},
|
|
134
136
|
bunite_init: {
|
|
135
|
-
args: [FFIType.cstring, FFIType.
|
|
137
|
+
args: [FFIType.cstring, FFIType.bool, FFIType.bool, FFIType.cstring],
|
|
136
138
|
returns: FFIType.bool
|
|
137
139
|
},
|
|
138
140
|
bunite_run_loop: {
|
|
139
141
|
args: [],
|
|
140
142
|
returns: FFIType.void
|
|
141
143
|
},
|
|
144
|
+
bunite_pump_once: {
|
|
145
|
+
args: [],
|
|
146
|
+
returns: FFIType.void
|
|
147
|
+
},
|
|
142
148
|
bunite_quit: {
|
|
143
149
|
args: [],
|
|
144
150
|
returns: FFIType.void
|
|
@@ -310,19 +316,6 @@ const nativeSymbolDefinitions = {
|
|
|
310
316
|
args: [FFIType.u32, FFIType.u32],
|
|
311
317
|
returns: FFIType.void
|
|
312
318
|
},
|
|
313
|
-
bunite_show_message_box: {
|
|
314
|
-
args: [
|
|
315
|
-
FFIType.u32,
|
|
316
|
-
FFIType.cstring,
|
|
317
|
-
FFIType.cstring,
|
|
318
|
-
FFIType.cstring,
|
|
319
|
-
FFIType.cstring,
|
|
320
|
-
FFIType.cstring,
|
|
321
|
-
FFIType.i32,
|
|
322
|
-
FFIType.i32
|
|
323
|
-
],
|
|
324
|
-
returns: FFIType.i32
|
|
325
|
-
},
|
|
326
319
|
bunite_set_webview_event_handler: {
|
|
327
320
|
args: [FFIType.function],
|
|
328
321
|
returns: FFIType.void
|
|
@@ -361,9 +354,7 @@ export function toCString(value: string): CStringPointer {
|
|
|
361
354
|
const normalized = value.endsWith("\0") ? value : `${value}\0`;
|
|
362
355
|
const buffer = Buffer.from(normalized, "utf8");
|
|
363
356
|
|
|
364
|
-
// Keep recent CString buffers alive
|
|
365
|
-
// This is not a long-term ownership model for retained native pointers, but it
|
|
366
|
-
// avoids immediate GC hazards across the current FFI call boundary.
|
|
357
|
+
// Keep recent CString buffers alive across the FFI call (not long-term retention).
|
|
367
358
|
retainedCStringBuffers.push(buffer);
|
|
368
359
|
if (retainedCStringBuffers.length > 1024) {
|
|
369
360
|
retainedCStringBuffers.shift();
|
|
@@ -373,20 +364,21 @@ export function toCString(value: string): CStringPointer {
|
|
|
373
364
|
}
|
|
374
365
|
|
|
375
366
|
function applyEnvironment(artifacts: ResolvedNativeArtifacts) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
367
|
+
// CEF needs engine dir on PATH (libcef.dll) and ICU_DATA pointing at resources. Null for mac/linux.
|
|
368
|
+
const engineBinaryDir = artifacts.engineDir && existsSync(join(artifacts.engineDir, "Release", "libcef.dll"))
|
|
369
|
+
? join(artifacts.engineDir, "Release")
|
|
370
|
+
: artifacts.engineDir;
|
|
371
|
+
const engineResourceDir = artifacts.engineDir && existsSync(join(artifacts.engineDir, "Resources", "resources.pak"))
|
|
372
|
+
? join(artifacts.engineDir, "Resources")
|
|
373
|
+
: artifacts.engineDir;
|
|
374
|
+
|
|
375
|
+
if (engineResourceDir && !process.env.ICU_DATA) {
|
|
376
|
+
process.env.ICU_DATA = engineResourceDir;
|
|
385
377
|
}
|
|
386
|
-
if (
|
|
378
|
+
if (engineBinaryDir) {
|
|
387
379
|
const pathEntries = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
|
|
388
|
-
if (!pathEntries.includes(
|
|
389
|
-
process.env.PATH = [
|
|
380
|
+
if (!pathEntries.includes(engineBinaryDir)) {
|
|
381
|
+
process.env.PATH = [engineBinaryDir, ...pathEntries].join(delimiter);
|
|
390
382
|
}
|
|
391
383
|
}
|
|
392
384
|
}
|
|
@@ -594,10 +586,7 @@ export async function initNativeRuntime(
|
|
|
594
586
|
const allowStub = options.allowStub ?? true;
|
|
595
587
|
const artifacts = resolveNativeArtifacts();
|
|
596
588
|
const hasNativeArtifacts = Boolean(
|
|
597
|
-
artifacts.nativeLibPath &&
|
|
598
|
-
artifacts.processHelperPath &&
|
|
599
|
-
existsSync(artifacts.nativeLibPath) &&
|
|
600
|
-
existsSync(artifacts.processHelperPath)
|
|
589
|
+
artifacts.nativeLibPath && existsSync(artifacts.nativeLibPath)
|
|
601
590
|
);
|
|
602
591
|
|
|
603
592
|
applyEnvironment(artifacts);
|
|
@@ -611,7 +600,7 @@ export async function initNativeRuntime(
|
|
|
611
600
|
nativeLibrary = hasNativeArtifacts ? tryLoadNativeLibrary(artifacts) : null;
|
|
612
601
|
|
|
613
602
|
if (nativeLibrary) {
|
|
614
|
-
const EXPECTED_ABI =
|
|
603
|
+
const EXPECTED_ABI = 4;
|
|
615
604
|
const nativeAbi = nativeLibrary.symbols.bunite_abi_version();
|
|
616
605
|
if (nativeAbi !== EXPECTED_ABI) {
|
|
617
606
|
throw new Error(
|
|
@@ -620,15 +609,14 @@ export async function initNativeRuntime(
|
|
|
620
609
|
);
|
|
621
610
|
}
|
|
622
611
|
registerNativeCallbacks(nativeLibrary);
|
|
623
|
-
const
|
|
624
|
-
? JSON.stringify(options.
|
|
612
|
+
const engineConfigJson = options.engineFlags
|
|
613
|
+
? JSON.stringify(options.engineFlags)
|
|
625
614
|
: "";
|
|
626
615
|
const initOk = nativeLibrary.symbols.bunite_init(
|
|
627
|
-
toCString(artifacts.
|
|
628
|
-
toCString(artifacts.cefDir ?? ""),
|
|
616
|
+
toCString(artifacts.engineDir ?? ""),
|
|
629
617
|
options.hideConsole ?? false,
|
|
630
618
|
options.popupBlocking ?? false,
|
|
631
|
-
toCString(
|
|
619
|
+
toCString(engineConfigJson)
|
|
632
620
|
);
|
|
633
621
|
|
|
634
622
|
if (!initOk) {
|
|
@@ -675,28 +663,16 @@ export function completePermissionRequest(requestId: number, stateValue: number)
|
|
|
675
663
|
nativeLibrary?.symbols.bunite_complete_permission_request(requestId, stateValue);
|
|
676
664
|
}
|
|
677
665
|
|
|
678
|
-
export function
|
|
679
|
-
type?: string;
|
|
680
|
-
title?: string;
|
|
681
|
-
message?: string;
|
|
682
|
-
detail?: string;
|
|
683
|
-
buttons?: string[];
|
|
684
|
-
defaultId?: number;
|
|
685
|
-
cancelId?: number;
|
|
686
|
-
}): number {
|
|
666
|
+
export function getNativeEngineName(): string | null {
|
|
687
667
|
const native = getNativeLibrary();
|
|
688
|
-
if (!native)
|
|
689
|
-
|
|
690
|
-
|
|
668
|
+
if (!native) return null;
|
|
669
|
+
const cstr = native.symbols.bunite_engine_name();
|
|
670
|
+
return cstr.toString();
|
|
671
|
+
}
|
|
691
672
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
toCString(params.detail ?? ""),
|
|
698
|
-
toCString((params.buttons ?? ["OK"]).join(messageBoxButtonSeparator)),
|
|
699
|
-
params.defaultId ?? 0,
|
|
700
|
-
params.cancelId ?? unsetCancelId
|
|
701
|
-
);
|
|
673
|
+
export function getNativeEngineVersion(): string | null {
|
|
674
|
+
const native = getNativeLibrary();
|
|
675
|
+
if (!native) return null;
|
|
676
|
+
const cstr = native.symbols.bunite_engine_version();
|
|
677
|
+
return cstr.toString();
|
|
702
678
|
}
|