electrobun 1.15.1 → 1.16.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.
@@ -4,6 +4,10 @@ const { execSync, spawn } = require('child_process');
4
4
  const { existsSync, mkdirSync, unlinkSync, chmodSync, copyFileSync, createWriteStream } = require('fs');
5
5
  const { join, dirname } = require('path');
6
6
  const https = require('https');
7
+ const ProxyAgent = require('proxy-agent').ProxyAgent;
8
+
9
+ // Create an HTTPS agent that respects environment proxy settings
10
+ const agent = new ProxyAgent();
7
11
 
8
12
  // Detect platform and architecture
9
13
  function getPlatform() {
@@ -38,7 +42,7 @@ async function downloadFile(url, filePath) {
38
42
  mkdirSync(dirname(filePath), { recursive: true });
39
43
  const file = createWriteStream(filePath);
40
44
 
41
- https.get(url, (response) => {
45
+ https.get(url, {agent}, (response) => {
42
46
  if (response.statusCode === 302 || response.statusCode === 301) {
43
47
  // Follow redirect
44
48
  return downloadFile(response.headers.location, filePath).then(resolve).catch(reject);
package/bun.lock CHANGED
@@ -8,6 +8,7 @@
8
8
  "@babylonjs/core": "^7.45.0",
9
9
  "@types/bun": "^1.3.8",
10
10
  "png-to-ico": "^2.1.8",
11
+ "proxy-agent": "^6.5.0",
11
12
  "rcedit": "^4.0.1",
12
13
  "three": "^0.165.0",
13
14
  },
@@ -21,38 +22,92 @@
21
22
 
22
23
  "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@1.1.1", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ=="],
23
24
 
25
+ "@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="],
26
+
24
27
  "@types/bun": ["@types/bun@1.3.8", "", { "dependencies": { "bun-types": "1.3.8" } }, "sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA=="],
25
28
 
26
29
  "@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="],
27
30
 
31
+ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
32
+
33
+ "ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="],
34
+
35
+ "basic-ftp": ["basic-ftp@5.2.0", "", {}, "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw=="],
36
+
28
37
  "bun-types": ["bun-types@1.3.8", "", { "dependencies": { "@types/node": "*" } }, "sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q=="],
29
38
 
30
39
  "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
31
40
 
32
41
  "cross-spawn-windows-exe": ["cross-spawn-windows-exe@1.2.0", "", { "dependencies": { "@malept/cross-spawn-promise": "^1.1.0", "is-wsl": "^2.2.0", "which": "^2.0.2" } }, "sha512-mkLtJJcYbDCxEG7Js6eUnUNndWjyUZwJ3H7bErmmtOYU/Zb99DyUkpamuIZE0b3bhmJyZ7D90uS6f+CGxRRjOw=="],
33
42
 
43
+ "data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="],
44
+
45
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
46
+
47
+ "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="],
48
+
49
+ "escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="],
50
+
51
+ "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
52
+
53
+ "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
54
+
55
+ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
56
+
57
+ "get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="],
58
+
59
+ "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
60
+
61
+ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
62
+
63
+ "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
64
+
34
65
  "is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
35
66
 
36
67
  "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
37
68
 
38
69
  "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
39
70
 
71
+ "lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="],
72
+
40
73
  "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
41
74
 
75
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
76
+
77
+ "netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="],
78
+
79
+ "pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="],
80
+
81
+ "pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="],
82
+
42
83
  "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
43
84
 
44
85
  "png-to-ico": ["png-to-ico@2.1.8", "", { "dependencies": { "@types/node": "^17.0.36", "minimist": "^1.2.6", "pngjs": "^6.0.0" }, "bin": { "png-to-ico": "bin/cli.js" } }, "sha512-Nf+IIn/cZ/DIZVdGveJp86NG5uNib1ZXMiDd/8x32HCTeKSvgpyg6D/6tUBn1QO/zybzoMK0/mc3QRgAyXdv9w=="],
45
86
 
46
87
  "pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
47
88
 
89
+ "proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="],
90
+
91
+ "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
92
+
48
93
  "rcedit": ["rcedit@4.0.1", "", { "dependencies": { "cross-spawn-windows-exe": "^1.1.0" } }, "sha512-bZdaQi34krFWhrDn+O53ccBDw0MkAT2Vhu75SqhtvhQu4OPyFM4RoVheyYiVQYdjhUi6EJMVWQ0tR6bCIYVkUg=="],
49
94
 
50
95
  "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
51
96
 
52
97
  "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
53
98
 
99
+ "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
100
+
101
+ "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="],
102
+
103
+ "socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
104
+
105
+ "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
106
+
54
107
  "three": ["three@0.165.0", "", {}, "sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA=="],
55
108
 
109
+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
110
+
56
111
  "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
57
112
 
58
113
  "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
@@ -73,6 +73,9 @@ interface WebviewTagElement extends HTMLElement {
73
73
  openDevTools(): void;
74
74
  closeDevTools(): void;
75
75
  toggleDevTools(): void;
76
+
77
+ // JavaScript execution
78
+ executeJavascript(js: string): void;
76
79
  }
77
80
 
78
81
  // Augment global types so querySelector('electrobun-webview') returns WebviewTagElement
@@ -58,6 +58,23 @@ export interface ElectrobunConfig {
58
58
  urlSchemes?: string[];
59
59
  };
60
60
 
61
+ /**
62
+ * Bunny Ears / Carrot packaging metadata.
63
+ * This lets an Electrobun app also declare how it should behave when distributed as a Carrot.
64
+ */
65
+ bunny?: {
66
+ carrot?: {
67
+ /**
68
+ * Carrot dependencies keyed by carrot id.
69
+ * Supports npm-style specifiers such as:
70
+ * - `file:../foundation-carrots/pty`
71
+ * - `workspace:*`
72
+ * - `^0.1.0`
73
+ */
74
+ dependencies?: Record<string, string>;
75
+ };
76
+ };
77
+
61
78
  /**
62
79
  * Build configuration options
63
80
  */
@@ -167,6 +184,20 @@ export interface ElectrobunConfig {
167
184
  */
168
185
  locales?: string[] | "*";
169
186
 
187
+ /**
188
+ * Additional file or directory paths to watch for changes during `electrobun dev --watch`.
189
+ * Paths are relative to the project root.
190
+ * Useful for files that affect your build but aren't listed as entrypoints or copy sources.
191
+ */
192
+ watch?: string[];
193
+
194
+ /**
195
+ * Glob patterns for files that should not trigger a rebuild when changed.
196
+ * Patterns are matched against project-relative paths.
197
+ * The `build/`, `artifacts/`, and `node_modules/` directories are always ignored automatically.
198
+ */
199
+ watchIgnore?: string[];
200
+
170
201
  /**
171
202
  * macOS-specific build configuration
172
203
  */
@@ -177,6 +208,13 @@ export interface ElectrobunConfig {
177
208
  */
178
209
  codesign?: boolean;
179
210
 
211
+ /**
212
+ * Create a DMG artifact for macOS builds.
213
+ * Disable this for local prototype builds that only need the app bundle and update archive.
214
+ * @default true
215
+ */
216
+ createDmg?: boolean;
217
+
180
218
  /**
181
219
  * Enable notarization for macOS builds (requires codesign)
182
220
  * @default false
@@ -204,17 +242,19 @@ export interface ElectrobunConfig {
204
242
  /**
205
243
  * Custom Chromium command-line flags to pass to CEF during initialization.
206
244
  * Keys are flag names without the "--" prefix.
207
- * Use `true` for switch-only flags, or a string for flags that take a value.
245
+ * - `true` add a switch-only flag
246
+ * - `"value"` — add a flag with a value
247
+ * - `false` — remove a default flag set by Electrobun
208
248
  *
209
249
  * @example
210
250
  * ```typescript
211
251
  * chromiumFlags: {
212
- * "disable-gpu": true, // --disable-gpu
252
+ * "disable-gpu": false, // remove Electrobun's default --disable-gpu
213
253
  * "remote-debugging-port": "9333", // --remote-debugging-port=9333
214
254
  * }
215
255
  * ```
216
256
  */
217
- chromiumFlags?: Record<string, string | true>;
257
+ chromiumFlags?: Record<string, string | boolean>;
218
258
 
219
259
  /**
220
260
  * macOS entitlements for code signing
@@ -253,17 +293,19 @@ export interface ElectrobunConfig {
253
293
  /**
254
294
  * Custom Chromium command-line flags to pass to CEF during initialization.
255
295
  * Keys are flag names without the "--" prefix.
256
- * Use `true` for switch-only flags, or a string for flags that take a value.
296
+ * - `true` add a switch-only flag
297
+ * - `"value"` — add a flag with a value
298
+ * - `false` — remove a default flag set by Electrobun
257
299
  *
258
300
  * @example
259
301
  * ```typescript
260
302
  * chromiumFlags: {
261
- * "disable-gpu": true, // --disable-gpu
303
+ * "disable-gpu": false, // remove Electrobun's default --disable-gpu
262
304
  * "remote-debugging-port": "9333", // --remote-debugging-port=9333
263
305
  * }
264
306
  * ```
265
307
  */
266
- chromiumFlags?: Record<string, string | true>;
308
+ chromiumFlags?: Record<string, string | boolean>;
267
309
 
268
310
  /**
269
311
  * Path to application icon (.ico format)
@@ -300,17 +342,19 @@ export interface ElectrobunConfig {
300
342
  /**
301
343
  * Custom Chromium command-line flags to pass to CEF during initialization.
302
344
  * Keys are flag names without the "--" prefix.
303
- * Use `true` for switch-only flags, or a string for flags that take a value.
345
+ * - `true` add a switch-only flag
346
+ * - `"value"` — add a flag with a value
347
+ * - `false` — remove a default flag set by Electrobun
304
348
  *
305
349
  * @example
306
350
  * ```typescript
307
351
  * chromiumFlags: {
308
- * "disable-gpu": true, // --disable-gpu
352
+ * "disable-gpu": false, // remove Electrobun's default --disable-gpu
309
353
  * "remote-debugging-port": "9333", // --remote-debugging-port=9333
310
354
  * }
311
355
  * ```
312
356
  */
313
- chromiumFlags?: Record<string, string | true>;
357
+ chromiumFlags?: Record<string, string | boolean>;
314
358
 
315
359
  /**
316
360
  * Path to application icon (PNG format recommended)
@@ -22,6 +22,7 @@ export type BrowserViewOptions<T = undefined> = {
22
22
  url: string | null;
23
23
  html: string | null;
24
24
  preload: string | null;
25
+ viewsRoot: string | null;
25
26
  renderer: "native" | "cef";
26
27
  partition: string | null;
27
28
  frame: {
@@ -54,6 +55,7 @@ const defaultOptions: Partial<BrowserViewOptions> = {
54
55
  url: null,
55
56
  html: null,
56
57
  preload: null,
58
+ viewsRoot: null,
57
59
  renderer: buildConfig.defaultRenderer,
58
60
  frame: {
59
61
  x: 0,
@@ -75,6 +77,7 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
75
77
  url: string | null = null;
76
78
  html: string | null = null;
77
79
  preload: string | null = null;
80
+ viewsRoot: string | null = null;
78
81
  partition: string | null = null;
79
82
  autoResize: boolean = true;
80
83
  frame: {
@@ -106,6 +109,7 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
106
109
  this.url = options.url || defaultOptions.url || null;
107
110
  this.html = options.html || defaultOptions.html || null;
108
111
  this.preload = options.preload || defaultOptions.preload || null;
112
+ this.viewsRoot = options.viewsRoot || defaultOptions.viewsRoot || null;
109
113
  this.frame = {
110
114
  x: options.frame?.x ?? defaultOptions.frame!.x,
111
115
  y: options.frame?.y ?? defaultOptions.frame!.y,
@@ -130,22 +134,11 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
130
134
  BrowserViewMap[this.id] = this;
131
135
  this.ptr = this.init() as Pointer;
132
136
 
133
- // If HTML content was provided, load it after webview creation
137
+ // If HTML content was provided, load it after webview creation.
134
138
  if (this.html) {
135
- console.log(
136
- `DEBUG: BrowserView constructor triggering loadHTML for webview ${this.id}`,
137
- );
138
- // Small delay to ensure webview is ready
139
139
  setTimeout(() => {
140
- console.log(
141
- `DEBUG: BrowserView delayed loadHTML for webview ${this.id}`,
142
- );
143
140
  this.loadHTML(this.html!);
144
- }, 100); // Back to 100ms since we fixed the race condition
145
- } else {
146
- console.log(
147
- `DEBUG: BrowserView constructor - no HTML provided for webview ${this.id}`,
148
- );
141
+ }, 100);
149
142
  }
150
143
  }
151
144
 
@@ -166,6 +159,7 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
166
159
  url: this.html ? null : this.url,
167
160
  html: this.html,
168
161
  preload: this.preload,
162
+ viewsRoot: this.viewsRoot,
169
163
  frame: {
170
164
  width: this.frame.width,
171
165
  height: this.frame.height,
@@ -279,6 +273,22 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
279
273
  native.symbols.webviewToggleDevTools(this.ptr);
280
274
  }
281
275
 
276
+ /**
277
+ * Set the page zoom level (WebKit only, similar to browser zoom).
278
+ * @param zoomLevel - The zoom level (1.0 = 100%, 1.5 = 150%, etc.)
279
+ */
280
+ setPageZoom(zoomLevel: number) {
281
+ native.symbols.webviewSetPageZoom(this.ptr, zoomLevel);
282
+ }
283
+
284
+ /**
285
+ * Get the current page zoom level.
286
+ * @returns The current zoom level (1.0 = 100%)
287
+ */
288
+ getPageZoom(): number {
289
+ return native.symbols.webviewGetPageZoom(this.ptr) as number;
290
+ }
291
+
282
292
  // todo (yoav): move this to a class that also has off, append, prepend, etc.
283
293
  // name should only allow browserView events
284
294
  // Note: normalize event names to willNavigate instead of ['will-navigate'] to save
@@ -22,6 +22,7 @@ export type WindowOptionsType<T = undefined> = {
22
22
  url: string | null;
23
23
  html: string | null;
24
24
  preload: string | null;
25
+ viewsRoot: string | null;
25
26
  renderer: "native" | "cef";
26
27
  rpc?: T;
27
28
  styleMask?: {};
@@ -32,6 +33,9 @@ export type WindowOptionsType<T = undefined> = {
32
33
  titleBarStyle: "hidden" | "hiddenInset" | "default";
33
34
  // transparent: when true, window background is transparent (see-through)
34
35
  transparent: boolean;
36
+ // passthrough: when true, mouse events pass through transparent regions
37
+ passthrough: boolean;
38
+ hidden?: boolean;
35
39
  navigationRules: string | null;
36
40
  // Sandbox mode: when true, disables RPC and only allows event emission
37
41
  // Use for untrusted content (remote URLs) to prevent malicious sites from
@@ -50,9 +54,12 @@ const defaultOptions: WindowOptionsType = {
50
54
  url: "https://electrobun.dev",
51
55
  html: null,
52
56
  preload: null,
57
+ viewsRoot: null,
53
58
  renderer: buildConfig.defaultRenderer,
54
59
  titleBarStyle: "default",
55
60
  transparent: false,
61
+ passthrough: false,
62
+ hidden: false,
56
63
  navigationRules: null,
57
64
  sandbox: false,
58
65
  };
@@ -111,8 +118,11 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
111
118
  url: string | null = null;
112
119
  html: string | null = null;
113
120
  preload: string | null = null;
121
+ viewsRoot: string | null = null;
114
122
  renderer: "native" | "cef" = "native";
115
123
  transparent: boolean = false;
124
+ passthrough: boolean = false;
125
+ hidden: boolean = false;
116
126
  navigationRules: string | null = null;
117
127
  // Sandbox mode disables RPC and only allows event emission (for untrusted content)
118
128
  sandbox: boolean = false;
@@ -138,8 +148,11 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
138
148
  this.url = options.url || null;
139
149
  this.html = options.html || null;
140
150
  this.preload = options.preload || null;
151
+ this.viewsRoot = options.viewsRoot || null;
141
152
  this.renderer = options.renderer || defaultOptions.renderer;
142
153
  this.transparent = options.transparent ?? false;
154
+ this.passthrough = options.passthrough ?? false;
155
+ this.hidden = options.hidden ?? false;
143
156
  this.navigationRules = options.navigationRules || null;
144
157
  this.sandbox = options.sandbox ?? false;
145
158
 
@@ -151,6 +164,7 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
151
164
  styleMask,
152
165
  titleBarStyle,
153
166
  transparent,
167
+ hidden,
154
168
  }: Partial<WindowOptionsType<T>>) {
155
169
  this.ptr = ffi.request.createWindow({
156
170
  id: this.id,
@@ -193,6 +207,7 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
193
207
  },
194
208
  titleBarStyle: titleBarStyle || "default",
195
209
  transparent: transparent ?? false,
210
+ hidden: hidden ?? false,
196
211
  }) as Pointer;
197
212
 
198
213
  BrowserWindowMap[this.id] = this;
@@ -206,6 +221,7 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
206
221
  url: this.url,
207
222
  html: this.html,
208
223
  preload: this.preload,
224
+ viewsRoot: this.viewsRoot,
209
225
  // frame: this.frame,
210
226
  renderer: this.renderer,
211
227
  frame: {
@@ -222,10 +238,9 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
222
238
  windowId: this.id,
223
239
  navigationRules: this.navigationRules,
224
240
  sandbox: this.sandbox,
241
+ startPassthrough: this.passthrough,
225
242
  });
226
243
 
227
- console.log("setting webviewId: ", webview.id);
228
-
229
244
  this.webviewId = webview.id;
230
245
  }
231
246
 
@@ -295,6 +310,14 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
295
310
  isAlwaysOnTop(): boolean {
296
311
  return ffi.request.isWindowAlwaysOnTop({ winId: this.id });
297
312
  }
313
+
314
+ setVisibleOnAllWorkspaces(visibleOnAllWorkspaces: boolean) {
315
+ return ffi.request.setWindowVisibleOnAllWorkspaces({ winId: this.id, visibleOnAllWorkspaces });
316
+ }
317
+
318
+ isVisibleOnAllWorkspaces(): boolean {
319
+ return ffi.request.isWindowVisibleOnAllWorkspaces({ winId: this.id });
320
+ }
298
321
 
299
322
  setPosition(x: number, y: number) {
300
323
  this.frame.x = x;
@@ -330,6 +353,22 @@ export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
330
353
  return { width: frame.width, height: frame.height };
331
354
  }
332
355
 
356
+ /**
357
+ * Set the page zoom level for the window's webview (WebKit only).
358
+ * @param zoomLevel - The zoom level (1.0 = 100%, 1.5 = 150%, etc.)
359
+ */
360
+ setPageZoom(zoomLevel: number) {
361
+ this.webview?.setPageZoom(zoomLevel);
362
+ }
363
+
364
+ /**
365
+ * Get the current page zoom level for the window's webview.
366
+ * @returns The current zoom level (1.0 = 100%)
367
+ */
368
+ getPageZoom(): number {
369
+ return this.webview?.getPageZoom() ?? 1.0;
370
+ }
371
+
333
372
  // todo (yoav): move this to a class that also has off, append, prepend, etc.
334
373
  // name should only allow browserWindow events
335
374
  on(name: string, handler: (event: unknown) => void) {
@@ -1,4 +1,8 @@
1
- // TODO: have a context specific menu that excludes role
1
+ // Context menus intentionally share ApplicationMenuItemConfig for now:
2
+ // - macOS supports role-based context menu items.
3
+ // - Windows has partial role behavior.
4
+ // - Linux showContextMenu is currently unsupported.
5
+ // Keep role typed here to avoid removing working macOS capability.
2
6
  import { ffi, type ApplicationMenuItemConfig } from "../proc/native";
3
7
  import electrobunEventEmitter from "../events/eventEmitter";
4
8
  import { roleLabelMap } from "./menuRoles";
@@ -1,4 +1,4 @@
1
- import { ffi, type MenuItemConfig } from "../proc/native";
1
+ import { ffi, type MenuItemConfig, type Rectangle } from "../proc/native";
2
2
  import electrobunEventEmitter from "../events/eventEmitter";
3
3
  import { VIEWS_FOLDER } from "./Paths";
4
4
  import { join } from "path";
@@ -23,6 +23,13 @@ export type TrayOptions = {
23
23
  export class Tray {
24
24
  id: number = nextTrayId++;
25
25
  ptr: Pointer | null = null;
26
+ visible = true;
27
+ title = "";
28
+ image = "";
29
+ template = true;
30
+ width = 16;
31
+ height = 16;
32
+ menu: Array<MenuItemConfig> | null = null;
26
33
 
27
34
  constructor({
28
35
  title = "",
@@ -31,24 +38,43 @@ export class Tray {
31
38
  width = 16,
32
39
  height = 16,
33
40
  }: TrayOptions = {}) {
41
+ this.title = title;
42
+ this.image = image;
43
+ this.template = template;
44
+ this.width = width;
45
+ this.height = height;
46
+
47
+ this.createNativeTray();
48
+
49
+ TrayMap[this.id] = this;
50
+ }
51
+
52
+ private createNativeTray() {
34
53
  try {
35
54
  this.ptr = ffi.request.createTray({
36
55
  id: this.id,
37
- title,
38
- image: this.resolveImagePath(image),
39
- template,
40
- width,
41
- height,
56
+ title: this.title,
57
+ image: this.resolveImagePath(this.image),
58
+ template: this.template,
59
+ width: this.width,
60
+ height: this.height,
42
61
  }) as Pointer;
62
+ this.visible = true;
43
63
  } catch (error) {
44
64
  console.warn("Tray creation failed:", error);
45
65
  console.warn(
46
66
  "System tray functionality may not be available on this platform",
47
67
  );
48
68
  this.ptr = null;
69
+ this.visible = false;
49
70
  }
50
71
 
51
- TrayMap[this.id] = this;
72
+ if (this.ptr && this.menu) {
73
+ ffi.request.setTrayMenu({
74
+ id: this.id,
75
+ menuConfig: JSON.stringify(menuConfigWithDefaults(this.menu)),
76
+ });
77
+ }
52
78
  }
53
79
 
54
80
  resolveImagePath(imgPath: string) {
@@ -61,11 +87,13 @@ export class Tray {
61
87
  }
62
88
 
63
89
  setTitle(title: string) {
90
+ this.title = title;
64
91
  if (!this.ptr) return;
65
92
  ffi.request.setTrayTitle({ id: this.id, title });
66
93
  }
67
94
 
68
95
  setImage(imgPath: string) {
96
+ this.image = imgPath;
69
97
  if (!this.ptr) return;
70
98
  ffi.request.setTrayImage({
71
99
  id: this.id,
@@ -74,6 +102,7 @@ export class Tray {
74
102
  }
75
103
 
76
104
  setMenu(menu: Array<MenuItemConfig>) {
105
+ this.menu = menu;
77
106
  if (!this.ptr) return;
78
107
  const menuWithDefaults = menuConfigWithDefaults(menu);
79
108
  ffi.request.setTrayMenu({
@@ -87,11 +116,34 @@ export class Tray {
87
116
  electrobunEventEmitter.on(specificName, handler);
88
117
  }
89
118
 
119
+ setVisible(visible: boolean) {
120
+ if (visible === this.visible) {
121
+ return;
122
+ }
123
+
124
+ if (!visible) {
125
+ if (this.ptr) {
126
+ ffi.request.removeTray({ id: this.id });
127
+ this.ptr = null;
128
+ }
129
+ this.visible = false;
130
+ return;
131
+ }
132
+
133
+ this.createNativeTray();
134
+ }
135
+
136
+ getBounds(): Rectangle {
137
+ return ffi.request.getTrayBounds({ id: this.id });
138
+ }
139
+
90
140
  remove() {
91
141
  console.log("Tray.remove() called for id:", this.id);
92
142
  if (this.ptr) {
93
143
  ffi.request.removeTray({ id: this.id });
144
+ this.ptr = null;
94
145
  }
146
+ this.visible = false;
95
147
  delete TrayMap[this.id];
96
148
  console.log("Tray removed from TrayMap");
97
149
  }
@@ -4,7 +4,7 @@ import {
4
4
  renameSync,
5
5
  unlinkSync,
6
6
  mkdirSync,
7
- rmdirSync,
7
+ rmSync,
8
8
  statSync,
9
9
  readdirSync,
10
10
  } from "fs";
@@ -145,7 +145,7 @@ function cleanupExtractionFolder(
145
145
  try {
146
146
  const s = statSync(fullPath);
147
147
  if (s.isDirectory()) {
148
- rmdirSync(fullPath, { recursive: true });
148
+ rmSync(fullPath, { recursive: true });
149
149
  } else {
150
150
  unlinkSync(fullPath);
151
151
  }
@@ -895,7 +895,7 @@ const Updater = {
895
895
  if (currentOS === "macos") {
896
896
  // Remove existing app before installing the new one
897
897
  if (statSync(runningAppBundlePath, { throwIfNoEntry: false })) {
898
- rmdirSync(runningAppBundlePath, { recursive: true });
898
+ rmSync(runningAppBundlePath, { recursive: true });
899
899
  }
900
900
 
901
901
  emitStatus("replacing-app", "Installing new version...");
@@ -920,7 +920,7 @@ const Updater = {
920
920
 
921
921
  // Remove existing app directory if it exists
922
922
  if (statSync(appBundleDir, { throwIfNoEntry: false })) {
923
- rmdirSync(appBundleDir, { recursive: true });
923
+ rmSync(appBundleDir, { recursive: true });
924
924
  }
925
925
 
926
926
  // Move new app bundle directory to app location
@@ -53,6 +53,14 @@ export const openPath = (path: string): boolean => {
53
53
  return ffi.request.openPath({ path });
54
54
  };
55
55
 
56
+ export const setDockIconVisible = (visible: boolean): void => {
57
+ ffi.request.setDockIconVisible({ visible });
58
+ };
59
+
60
+ export const isDockIconVisible = (): boolean => {
61
+ return ffi.request.isDockIconVisible();
62
+ };
63
+
56
64
  export type NotificationOptions = {
57
65
  /**
58
66
  * The title of the notification (required)
@@ -16,6 +16,7 @@ export default {
16
16
  ),
17
17
  openUrl: (data: OpenUrlData) =>
18
18
  new ElectrobunEvent<OpenUrlData, void>("open-url", data),
19
+ reopen: (data: {}) => new ElectrobunEvent<{}, void>("reopen", data),
19
20
  beforeQuit: (data: {}) =>
20
21
  new ElectrobunEvent<{}, { allow: boolean }>("before-quit", data),
21
22
  };