electrobun 1.16.1-beta.0 → 1.17.0-beta.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/dist/api/bun/ElectrobunConfig.ts +16 -0
- package/dist/api/bun/core/BrowserView.ts +12 -12
- package/dist/api/bun/core/Updater.ts +2 -4
- package/dist/api/bun/core/Utils.ts +18 -15
- package/dist/api/bun/index.ts +137 -0
- package/dist/api/bun/preload/build.ts +6 -4
- package/dist/api/bun/proc/native.ts +270 -237
- package/dist/api/shared/bun-version.ts +1 -1
- package/package.json +3 -2
- package/src/cli/index.ts +148 -14
|
@@ -198,6 +198,22 @@ export interface ElectrobunConfig {
|
|
|
198
198
|
*/
|
|
199
199
|
watchIgnore?: string[];
|
|
200
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Carrot build configuration.
|
|
203
|
+
* When present, the build also produces a carrot artifact alongside the standalone app.
|
|
204
|
+
* Set `carrotOnly: true` to skip the standalone app build entirely.
|
|
205
|
+
*/
|
|
206
|
+
carrot?: {
|
|
207
|
+
id: string;
|
|
208
|
+
name: string;
|
|
209
|
+
description?: string;
|
|
210
|
+
mode?: "window" | "background";
|
|
211
|
+
carrotOnly?: boolean;
|
|
212
|
+
permissions?: Record<string, unknown>;
|
|
213
|
+
dependencies?: Record<string, string>;
|
|
214
|
+
remoteUIs?: Record<string, { entrypoint: string; [key: string]: unknown }>;
|
|
215
|
+
};
|
|
216
|
+
|
|
201
217
|
/**
|
|
202
218
|
* macOS-specific build configuration
|
|
203
219
|
*/
|
|
@@ -225,7 +225,7 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
225
225
|
loadURL(url: string) {
|
|
226
226
|
console.log(`DEBUG: loadURL called for webview ${this.id}: ${url}`);
|
|
227
227
|
this.url = url;
|
|
228
|
-
native
|
|
228
|
+
native!.symbols.loadURLInWebView(this.ptr, toCString(this.url));
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
loadHTML(html: string) {
|
|
@@ -237,18 +237,18 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
237
237
|
|
|
238
238
|
if (this.renderer === "cef") {
|
|
239
239
|
// For CEF, store HTML content in native map and use scheme handler
|
|
240
|
-
native
|
|
240
|
+
native!.symbols.setWebviewHTMLContent(this.id, toCString(html));
|
|
241
241
|
this.loadURL("views://internal/index.html");
|
|
242
242
|
} else {
|
|
243
243
|
// For WKWebView, load HTML content directly
|
|
244
|
-
native
|
|
244
|
+
native!.symbols.loadHTMLInWebView(this.ptr, toCString(html));
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
setNavigationRules(rules: string[]) {
|
|
249
249
|
this.navigationRules = JSON.stringify(rules);
|
|
250
250
|
const rulesJson = JSON.stringify(rules);
|
|
251
|
-
native
|
|
251
|
+
native!.symbols.setWebviewNavigationRules(this.ptr, toCString(rulesJson));
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
findInPage(
|
|
@@ -257,7 +257,7 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
257
257
|
) {
|
|
258
258
|
const forward = options?.forward ?? true;
|
|
259
259
|
const matchCase = options?.matchCase ?? false;
|
|
260
|
-
native
|
|
260
|
+
native!.symbols.webviewFindInPage(
|
|
261
261
|
this.ptr,
|
|
262
262
|
toCString(searchText),
|
|
263
263
|
forward,
|
|
@@ -266,19 +266,19 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
stopFindInPage() {
|
|
269
|
-
native
|
|
269
|
+
native!.symbols.webviewStopFind(this.ptr);
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
openDevTools() {
|
|
273
|
-
native
|
|
273
|
+
native!.symbols.webviewOpenDevTools(this.ptr);
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
closeDevTools() {
|
|
277
|
-
native
|
|
277
|
+
native!.symbols.webviewCloseDevTools(this.ptr);
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
toggleDevTools() {
|
|
281
|
-
native
|
|
281
|
+
native!.symbols.webviewToggleDevTools(this.ptr);
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
/**
|
|
@@ -286,7 +286,7 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
286
286
|
* @param zoomLevel - The zoom level (1.0 = 100%, 1.5 = 150%, etc.)
|
|
287
287
|
*/
|
|
288
288
|
setPageZoom(zoomLevel: number) {
|
|
289
|
-
native
|
|
289
|
+
native!.symbols.webviewSetPageZoom(this.ptr, zoomLevel);
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
/**
|
|
@@ -294,7 +294,7 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
294
294
|
* @returns The current zoom level (1.0 = 100%)
|
|
295
295
|
*/
|
|
296
296
|
getPageZoom(): number {
|
|
297
|
-
return native
|
|
297
|
+
return native!.symbols.webviewGetPageZoom(this.ptr) as number;
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
// todo (yoav): move this to a class that also has off, append, prepend, etc.
|
|
@@ -362,7 +362,7 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
362
362
|
});
|
|
363
363
|
this.rpcHandler = undefined;
|
|
364
364
|
this.ptr = null as any;
|
|
365
|
-
native
|
|
365
|
+
native!.symbols.webviewRemove(ptr);
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
static getById(id: number) {
|
|
@@ -1119,11 +1119,9 @@ del "%~f0"
|
|
|
1119
1119
|
localInfo = await Bun.file(`../${resourcesDir}/version.json`).json();
|
|
1120
1120
|
return localInfo;
|
|
1121
1121
|
} catch (error) {
|
|
1122
|
-
// Handle the error
|
|
1123
1122
|
console.error("Failed to read version.json", error);
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
throw error;
|
|
1123
|
+
localInfo = { identifier: "", channel: "", version: "", hash: "", baseUrl: "", name: "" };
|
|
1124
|
+
return localInfo;
|
|
1127
1125
|
}
|
|
1128
1126
|
},
|
|
1129
1127
|
};
|
|
@@ -136,25 +136,27 @@ export const quit = () => {
|
|
|
136
136
|
return;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// a Worker thread can fail to terminate when the main thread is blocked in FFI.
|
|
147
|
-
native.symbols.forceExit(0);
|
|
139
|
+
if (native) {
|
|
140
|
+
native.symbols.stopEventLoop();
|
|
141
|
+
native.symbols.waitForShutdownComplete(5000);
|
|
142
|
+
native.symbols.forceExit(0);
|
|
143
|
+
} else {
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
148
146
|
};
|
|
149
147
|
|
|
150
148
|
// Override process.exit so that calling it triggers proper native cleanup
|
|
149
|
+
const _originalProcessExit = process.exit;
|
|
151
150
|
process.exit = ((code?: number) => {
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
151
|
+
if (native) {
|
|
152
|
+
if (isQuitting) {
|
|
153
|
+
native.symbols.forceExit(code ?? 0);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
quit();
|
|
157
|
+
} else {
|
|
158
|
+
_originalProcessExit(code ?? 0);
|
|
156
159
|
}
|
|
157
|
-
quit();
|
|
158
160
|
}) as typeof process.exit;
|
|
159
161
|
|
|
160
162
|
export const openFileDialog = async (
|
|
@@ -358,7 +360,8 @@ function getVersionInfo(): { identifier: string; channel: string } {
|
|
|
358
360
|
return _versionInfo;
|
|
359
361
|
} catch (error) {
|
|
360
362
|
console.error("Failed to read version.json", error);
|
|
361
|
-
|
|
363
|
+
_versionInfo = { identifier: "", channel: "" };
|
|
364
|
+
return _versionInfo;
|
|
362
365
|
}
|
|
363
366
|
}
|
|
364
367
|
|
package/dist/api/bun/index.ts
CHANGED
|
@@ -44,6 +44,143 @@ import type {
|
|
|
44
44
|
ApplicationMenuItemConfig,
|
|
45
45
|
} from "./proc/native";
|
|
46
46
|
import { BuildConfig, type BuildConfigType } from "./core/BuildConfig";
|
|
47
|
+
import { bridge, hasFFI } from "./proc/native";
|
|
48
|
+
|
|
49
|
+
// Carrot boot state — populated from __bunnyCarrotBootstrap injected by Bunny Ears
|
|
50
|
+
let _carrotManifest: Record<string, unknown> | null = null;
|
|
51
|
+
let _carrotContext: { currentDir?: string; statePath?: string; logsPath?: string; permissions?: string[]; grantedPermissions?: Record<string, unknown>; authToken?: string | null; channel?: string } | null = null;
|
|
52
|
+
|
|
53
|
+
const _bootstrap = (globalThis as any).__bunnyCarrotBootstrap as { manifest?: any; context?: any } | undefined;
|
|
54
|
+
if (_bootstrap) {
|
|
55
|
+
_carrotManifest = _bootstrap.manifest ?? null;
|
|
56
|
+
_carrotContext = _bootstrap.context ?? null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (bridge) {
|
|
60
|
+
bridge.on("init", (payload: any) => {
|
|
61
|
+
if (payload?.manifest) _carrotManifest = payload.manifest;
|
|
62
|
+
if (payload?.context) _carrotContext = payload.context;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Forward host events to the local event emitter so ApplicationMenu.on(),
|
|
66
|
+
// ContextMenu.on(), etc. work in carrot workers
|
|
67
|
+
for (const eventName of ["application-menu-clicked", "context-menu-clicked"]) {
|
|
68
|
+
bridge.on(eventName, (payload: unknown) => {
|
|
69
|
+
electobunEventEmmitter.emitEvent({ type: eventName, data: payload } as any);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Update local auth token when the host notifies of a change (e.g., Farm login)
|
|
74
|
+
bridge.on("auth-token-changed", (payload: unknown) => {
|
|
75
|
+
const token = (payload as any)?.token;
|
|
76
|
+
if (token && _carrotContext) {
|
|
77
|
+
_carrotContext.authToken = token;
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Clear local auth token on logout
|
|
82
|
+
bridge.on("auth-token-cleared", () => {
|
|
83
|
+
if (_carrotContext) {
|
|
84
|
+
_carrotContext.authToken = null;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const Carrots = {
|
|
90
|
+
async invoke<T = unknown>(
|
|
91
|
+
carrotId: string,
|
|
92
|
+
method: string,
|
|
93
|
+
params?: unknown,
|
|
94
|
+
options?: { windowId?: string },
|
|
95
|
+
): Promise<T> {
|
|
96
|
+
if (!bridge) throw new Error("Carrots.invoke() is only available when running as a carrot inside Bunny Ears");
|
|
97
|
+
return bridge.requestHost<T>("invoke-carrot", { carrotId, method, params, windowId: options?.windowId });
|
|
98
|
+
},
|
|
99
|
+
emit(carrotId: string, name: string, payload?: unknown) {
|
|
100
|
+
if (!bridge) throw new Error("Carrots.emit() is only available when running as a carrot inside Bunny Ears");
|
|
101
|
+
bridge.sendAction("emit-carrot-event", { carrotId, name, payload });
|
|
102
|
+
},
|
|
103
|
+
async list() {
|
|
104
|
+
if (!bridge) throw new Error("Carrots.list() is only available when running as a carrot inside Bunny Ears");
|
|
105
|
+
return bridge.requestHost<Array<{
|
|
106
|
+
id: string; name: string; description: string; version: string;
|
|
107
|
+
mode: string; permissions: string[]; status: string; devMode: boolean;
|
|
108
|
+
}>>("list-carrots");
|
|
109
|
+
},
|
|
110
|
+
async start(carrotId: string) {
|
|
111
|
+
if (!bridge) throw new Error("Carrots.start() is only available when running as a carrot inside Bunny Ears");
|
|
112
|
+
return bridge.requestHost<{ ok: boolean }>("start-carrot", { id: carrotId });
|
|
113
|
+
},
|
|
114
|
+
async stop(carrotId: string) {
|
|
115
|
+
if (!bridge) throw new Error("Carrots.stop() is only available when running as a carrot inside Bunny Ears");
|
|
116
|
+
return bridge.requestHost<{ ok: boolean }>("stop-carrot", { id: carrotId });
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const app = {
|
|
121
|
+
on(name: string, handler: (payload: unknown) => void) {
|
|
122
|
+
if (bridge) {
|
|
123
|
+
return bridge.on(name, handler);
|
|
124
|
+
}
|
|
125
|
+
electobunEventEmmitter.on(name, (e: { data: unknown }) => handler(e.data));
|
|
126
|
+
return () => {};
|
|
127
|
+
},
|
|
128
|
+
quit() {
|
|
129
|
+
Utils.quit();
|
|
130
|
+
},
|
|
131
|
+
get isCarrotMode() {
|
|
132
|
+
return !hasFFI;
|
|
133
|
+
},
|
|
134
|
+
get manifest() {
|
|
135
|
+
return _carrotManifest;
|
|
136
|
+
},
|
|
137
|
+
get permissions() {
|
|
138
|
+
return _carrotContext?.permissions ?? [];
|
|
139
|
+
},
|
|
140
|
+
get grantedPermissions() {
|
|
141
|
+
return _carrotContext?.grantedPermissions ?? {};
|
|
142
|
+
},
|
|
143
|
+
get currentDir() {
|
|
144
|
+
return _carrotContext?.currentDir ?? "";
|
|
145
|
+
},
|
|
146
|
+
get statePath() {
|
|
147
|
+
return _carrotContext?.statePath ?? "";
|
|
148
|
+
},
|
|
149
|
+
get logsPath() {
|
|
150
|
+
return _carrotContext?.logsPath ?? "";
|
|
151
|
+
},
|
|
152
|
+
get authToken() {
|
|
153
|
+
return _carrotContext?.authToken ?? null;
|
|
154
|
+
},
|
|
155
|
+
async fetchAuthToken(): Promise<string | null> {
|
|
156
|
+
if (!bridge) return null;
|
|
157
|
+
const result = await bridge.requestHost<{ token: string | null }>("get-auth-token");
|
|
158
|
+
if (result?.token && _carrotContext) {
|
|
159
|
+
_carrotContext.authToken = result.token;
|
|
160
|
+
}
|
|
161
|
+
return result?.token ?? null;
|
|
162
|
+
},
|
|
163
|
+
async setAuthToken(token: string): Promise<void> {
|
|
164
|
+
if (!bridge) return;
|
|
165
|
+
await bridge.requestHost("set-auth-token", { token });
|
|
166
|
+
if (_carrotContext) {
|
|
167
|
+
_carrotContext.authToken = token;
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
get channel() {
|
|
171
|
+
return _carrotContext?.channel ?? "";
|
|
172
|
+
},
|
|
173
|
+
openManager() {
|
|
174
|
+
if (bridge) bridge.sendAction("open-manager");
|
|
175
|
+
},
|
|
176
|
+
openBunnyWindow(payload?: { screenX?: number; screenY?: number }) {
|
|
177
|
+
if (bridge) bridge.sendAction("open-bunny-window", payload);
|
|
178
|
+
},
|
|
179
|
+
async getWindowFrame(windowId?: string) {
|
|
180
|
+
if (!bridge) return null;
|
|
181
|
+
return bridge.requestHost<{ x: number; y: number; width: number; height: number } | null>("window-get-frame", { windowId });
|
|
182
|
+
},
|
|
183
|
+
};
|
|
47
184
|
|
|
48
185
|
// Named Exports
|
|
49
186
|
export {
|
|
@@ -17,7 +17,7 @@ async function buildPreload() {
|
|
|
17
17
|
const fullResult = await Bun.build({
|
|
18
18
|
entrypoints: [fullPreloadEntry],
|
|
19
19
|
target: "browser",
|
|
20
|
-
format: "
|
|
20
|
+
format: "esm",
|
|
21
21
|
minify: false,
|
|
22
22
|
});
|
|
23
23
|
|
|
@@ -31,7 +31,7 @@ async function buildPreload() {
|
|
|
31
31
|
const sandboxedResult = await Bun.build({
|
|
32
32
|
entrypoints: [sandboxedPreloadEntry],
|
|
33
33
|
target: "browser",
|
|
34
|
-
format: "
|
|
34
|
+
format: "esm",
|
|
35
35
|
minify: false,
|
|
36
36
|
});
|
|
37
37
|
|
|
@@ -40,8 +40,10 @@ async function buildPreload() {
|
|
|
40
40
|
throw new Error("Failed to build sandboxed preload script");
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
// Bun does not currently support iife output, so we wrap the ESM bundle manually
|
|
44
|
+
// to keep preload globals scoped for script injection.
|
|
45
|
+
const fullPreloadJs = `(function(){${await fullResult.outputs[0]!.text()}})();`;
|
|
46
|
+
const sandboxedPreloadJs = `(function(){${await sandboxedResult.outputs[0]!.text()}})();`;
|
|
45
47
|
|
|
46
48
|
const outputContent = `// Auto-generated file. Do not edit directly.
|
|
47
49
|
// Run "bun build.ts" or "bun build:dev" from the package folder to regenerate.
|