electrobun 0.0.19-beta.13 → 0.0.19-beta.130

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.
Files changed (100) hide show
  1. package/BUILD.md +90 -0
  2. package/README.md +1 -1
  3. package/bin/electrobun.cjs +2 -9
  4. package/debug.js +5 -0
  5. package/dist/api/browser/builtinrpcSchema.ts +19 -0
  6. package/dist/api/browser/index.ts +409 -0
  7. package/dist/api/browser/rpc/webview.ts +79 -0
  8. package/dist/api/browser/stylesAndElements.ts +3 -0
  9. package/dist/api/browser/webviewtag.ts +586 -0
  10. package/dist/api/bun/ElectrobunConfig.ts +171 -0
  11. package/dist/api/bun/core/ApplicationMenu.ts +66 -0
  12. package/dist/api/bun/core/BrowserView.ts +349 -0
  13. package/dist/api/bun/core/BrowserWindow.ts +195 -0
  14. package/dist/api/bun/core/ContextMenu.ts +67 -0
  15. package/dist/api/bun/core/Paths.ts +5 -0
  16. package/dist/api/bun/core/Socket.ts +181 -0
  17. package/dist/api/bun/core/Tray.ts +121 -0
  18. package/dist/api/bun/core/Updater.ts +681 -0
  19. package/dist/api/bun/core/Utils.ts +48 -0
  20. package/dist/api/bun/events/ApplicationEvents.ts +14 -0
  21. package/dist/api/bun/events/event.ts +29 -0
  22. package/dist/api/bun/events/eventEmitter.ts +45 -0
  23. package/dist/api/bun/events/trayEvents.ts +9 -0
  24. package/dist/api/bun/events/webviewEvents.ts +16 -0
  25. package/dist/api/bun/events/windowEvents.ts +12 -0
  26. package/dist/api/bun/index.ts +47 -0
  27. package/dist/api/bun/proc/linux.md +43 -0
  28. package/dist/api/bun/proc/native.ts +1322 -0
  29. package/dist/api/shared/platform.ts +48 -0
  30. package/dist/main.js +54 -0
  31. package/package.json +11 -6
  32. package/src/cli/index.ts +1353 -239
  33. package/templates/hello-world/README.md +57 -0
  34. package/templates/hello-world/bun.lock +225 -0
  35. package/templates/hello-world/electrobun.config.ts +28 -0
  36. package/templates/hello-world/package.json +16 -0
  37. package/templates/hello-world/src/bun/index.ts +15 -0
  38. package/templates/hello-world/src/mainview/index.css +124 -0
  39. package/templates/hello-world/src/mainview/index.html +46 -0
  40. package/templates/hello-world/src/mainview/index.ts +1 -0
  41. package/templates/interactive-playground/README.md +26 -0
  42. package/templates/interactive-playground/assets/tray-icon.png +0 -0
  43. package/templates/interactive-playground/electrobun.config.ts +36 -0
  44. package/templates/interactive-playground/package-lock.json +36 -0
  45. package/templates/interactive-playground/package.json +15 -0
  46. package/templates/interactive-playground/src/bun/demos/files.ts +70 -0
  47. package/templates/interactive-playground/src/bun/demos/menus.ts +139 -0
  48. package/templates/interactive-playground/src/bun/demos/rpc.ts +83 -0
  49. package/templates/interactive-playground/src/bun/demos/system.ts +72 -0
  50. package/templates/interactive-playground/src/bun/demos/updates.ts +105 -0
  51. package/templates/interactive-playground/src/bun/demos/windows.ts +90 -0
  52. package/templates/interactive-playground/src/bun/index.ts +124 -0
  53. package/templates/interactive-playground/src/bun/types/rpc.ts +109 -0
  54. package/templates/interactive-playground/src/mainview/components/EventLog.ts +107 -0
  55. package/templates/interactive-playground/src/mainview/components/Sidebar.ts +65 -0
  56. package/templates/interactive-playground/src/mainview/components/Toast.ts +57 -0
  57. package/templates/interactive-playground/src/mainview/demos/FileDemo.ts +211 -0
  58. package/templates/interactive-playground/src/mainview/demos/MenuDemo.ts +102 -0
  59. package/templates/interactive-playground/src/mainview/demos/RPCDemo.ts +229 -0
  60. package/templates/interactive-playground/src/mainview/demos/TrayDemo.ts +132 -0
  61. package/templates/interactive-playground/src/mainview/demos/WebViewDemo.ts +411 -0
  62. package/templates/interactive-playground/src/mainview/demos/WindowDemo.ts +207 -0
  63. package/templates/interactive-playground/src/mainview/index.css +538 -0
  64. package/templates/interactive-playground/src/mainview/index.html +103 -0
  65. package/templates/interactive-playground/src/mainview/index.ts +238 -0
  66. package/templates/multitab-browser/README.md +34 -0
  67. package/templates/multitab-browser/bun.lock +224 -0
  68. package/templates/multitab-browser/electrobun.config.ts +32 -0
  69. package/templates/multitab-browser/package-lock.json +20 -0
  70. package/templates/multitab-browser/package.json +12 -0
  71. package/templates/multitab-browser/src/bun/index.ts +144 -0
  72. package/templates/multitab-browser/src/bun/tabManager.ts +200 -0
  73. package/templates/multitab-browser/src/bun/types/rpc.ts +78 -0
  74. package/templates/multitab-browser/src/mainview/index.css +487 -0
  75. package/templates/multitab-browser/src/mainview/index.html +94 -0
  76. package/templates/multitab-browser/src/mainview/index.ts +630 -0
  77. package/templates/photo-booth/README.md +108 -0
  78. package/templates/photo-booth/bun.lock +239 -0
  79. package/templates/photo-booth/electrobun.config.ts +28 -0
  80. package/templates/photo-booth/package.json +16 -0
  81. package/templates/photo-booth/src/bun/index.ts +92 -0
  82. package/templates/photo-booth/src/mainview/index.css +465 -0
  83. package/templates/photo-booth/src/mainview/index.html +124 -0
  84. package/templates/photo-booth/src/mainview/index.ts +499 -0
  85. package/tests/bun.lock +14 -0
  86. package/tests/electrobun.config.ts +45 -0
  87. package/tests/package-lock.json +36 -0
  88. package/tests/package.json +13 -0
  89. package/tests/src/bun/index.ts +100 -0
  90. package/tests/src/bun/test-runner.ts +508 -0
  91. package/tests/src/mainview/index.html +110 -0
  92. package/tests/src/mainview/index.ts +458 -0
  93. package/tests/src/mainview/styles/main.css +451 -0
  94. package/tests/src/testviews/tray-test.html +57 -0
  95. package/tests/src/testviews/webview-mask.html +114 -0
  96. package/tests/src/testviews/webview-navigation.html +36 -0
  97. package/tests/src/testviews/window-create.html +17 -0
  98. package/tests/src/testviews/window-events.html +29 -0
  99. package/tests/src/testviews/window-focus.html +37 -0
  100. package/tests/src/webviewtag/index.ts +11 -0
@@ -0,0 +1,1322 @@
1
+ import { join, resolve } from "path";
2
+ import { type RPCSchema, type RPCTransport, createRPC } from "rpc-anywhere";
3
+ import { execSync } from "child_process";
4
+ import * as fs from "fs";
5
+ import electrobunEventEmitter from "../events/eventEmitter";
6
+ import { BrowserView } from "../core/BrowserView";
7
+ import { Updater } from "../core/Updater";
8
+ import { Tray } from "../core/Tray";
9
+
10
+
11
+
12
+ // todo: set up FFI, this is already in the webworker.
13
+
14
+ import { dirname } from "path";
15
+ import { dlopen, suffix, JSCallback, CString, ptr, FFIType, toArrayBuffer } from "bun:ffi";
16
+ import { BrowserWindow, BrowserWindowMap } from "../core/BrowserWindow";
17
+
18
+
19
+
20
+ export const native = (() => {
21
+ try {
22
+ return dlopen(`./libNativeWrapper.${suffix}`, {
23
+ // window
24
+ createWindowWithFrameAndStyleFromWorker: {
25
+ // Pass each parameter individually
26
+ args: [
27
+ FFIType.u32, // windowId
28
+ FFIType.f64, FFIType.f64, // x, y
29
+ FFIType.f64, FFIType.f64, // width, height
30
+ FFIType.u32, // styleMask
31
+ FFIType.cstring, // titleBarStyle
32
+ FFIType.function, // closeHandler
33
+ FFIType.function, // moveHandler
34
+ FFIType.function // resizeHandler
35
+
36
+ ],
37
+ returns: FFIType.ptr
38
+ },
39
+ setNSWindowTitle: {
40
+ args: [
41
+ FFIType.ptr, // window ptr
42
+ FFIType.cstring, // title
43
+ ],
44
+ returns: FFIType.void,
45
+ },
46
+ makeNSWindowKeyAndOrderFront: {
47
+ args: [
48
+ FFIType.ptr, // window ptr
49
+ ],
50
+ returns: FFIType.void,
51
+ },
52
+ closeNSWindow: {
53
+ args: [
54
+ FFIType.ptr, // window ptr
55
+ ],
56
+ returns: FFIType.void,
57
+ },
58
+ // webview
59
+ initWebview: {
60
+ args: [
61
+ FFIType.u32, // webviewId
62
+ FFIType.ptr, // windowPtr
63
+ FFIType.cstring, // renderer
64
+ FFIType.cstring, // url
65
+ FFIType.f64, FFIType.f64, // x, y
66
+ FFIType.f64, FFIType.f64, // width, height
67
+ FFIType.bool, // autoResize
68
+ FFIType.cstring, // partition
69
+ FFIType.function, // decideNavigation: *const fn (u32, [*:0]const u8) callconv(.C) bool,
70
+ FFIType.function, // webviewEventHandler: *const fn (u32, [*:0]const u8, [*:0]const u8) callconv(.C) void,
71
+ FFIType.function, // bunBridgePostmessageHandler: *const fn (u32, [*:0]const u8) callconv(.C) void,
72
+ FFIType.function, // internalBridgeHandler: *const fn (u32, [*:0]const u8) callconv(.C) void,
73
+ FFIType.cstring, // electrobunPreloadScript
74
+ FFIType.cstring, // customPreloadScript
75
+ ],
76
+ returns: FFIType.ptr
77
+ },
78
+
79
+ // webviewtag
80
+ webviewCanGoBack: {
81
+ args: [FFIType.ptr],
82
+ returns: FFIType.bool
83
+ },
84
+
85
+ webviewCanGoForward: {
86
+ args: [FFIType.ptr],
87
+ returns: FFIType.bool
88
+ },
89
+ // TODO: Curently CEF doesn't support this directly
90
+ // revisit after refactor
91
+ // callAsyncJavaScript: {
92
+ // args: [
93
+ // FFIType.
94
+ // ],
95
+ // returns: FFIType.void
96
+ // },
97
+ resizeWebview: {
98
+ args: [
99
+ FFIType.ptr, // webview handle
100
+ FFIType.f64, // x
101
+ FFIType.f64, // y
102
+ FFIType.f64, // width
103
+ FFIType.f64, // height
104
+ FFIType.cstring // maskJson
105
+ ],
106
+ returns: FFIType.void
107
+ },
108
+
109
+ loadURLInWebView: {
110
+ args: [FFIType.ptr, FFIType.cstring],
111
+ returns: FFIType.void
112
+ },
113
+
114
+ updatePreloadScriptToWebView: {
115
+ args: [
116
+ FFIType.ptr, // webview handle
117
+ FFIType.cstring, // script identifier
118
+ FFIType.cstring, // script
119
+ FFIType.bool // allframes
120
+ ],
121
+ returns: FFIType.void
122
+ },
123
+ webviewGoBack: {
124
+ args: [FFIType.ptr],
125
+ returns: FFIType.void
126
+ },
127
+ webviewGoForward: {
128
+ args: [FFIType.ptr],
129
+ returns: FFIType.void
130
+ },
131
+ webviewReload: {
132
+ args: [FFIType.ptr],
133
+ returns: FFIType.void
134
+ },
135
+ webviewRemove: {
136
+ args: [FFIType.ptr],
137
+ returns: FFIType.void
138
+ },
139
+ startWindowMove: {
140
+ args: [FFIType.ptr],
141
+ returns: FFIType.void
142
+ },
143
+ stopWindowMove: {
144
+ args: [],
145
+ returns: FFIType.void
146
+ },
147
+ webviewSetTransparent: {
148
+ // TODO XX: bools or ints?
149
+ args: [FFIType.ptr, FFIType.bool],
150
+ returns: FFIType.void
151
+ },
152
+ webviewSetPassthrough: {
153
+ args: [FFIType.ptr, FFIType.bool],
154
+ returns: FFIType.void
155
+ },
156
+ webviewSetHidden: {
157
+ args: [FFIType.ptr, FFIType.bool],
158
+ returns: FFIType.void
159
+ },
160
+ evaluateJavaScriptWithNoCompletion: {
161
+ args: [FFIType.ptr, FFIType.cstring],
162
+ returns: FFIType.void
163
+ },
164
+ // Tray
165
+ createTray: {
166
+ args: [
167
+ FFIType.u32, // id
168
+ FFIType.cstring, // title
169
+ FFIType.cstring, // pathToImage
170
+ FFIType.bool, // isTemplate
171
+ FFIType.u32, // width
172
+ FFIType.u32, //height
173
+ FFIType.function, // trayItemHandler
174
+ ],
175
+ returns: FFIType.ptr
176
+ },
177
+ setTrayTitle: {
178
+ args: [FFIType.ptr, FFIType.cstring],
179
+ returns: FFIType.void
180
+ },
181
+ setTrayImage: {
182
+ args: [FFIType.ptr, FFIType.cstring],
183
+ returns: FFIType.void
184
+ },
185
+ setTrayMenu: {
186
+ args: [FFIType.ptr, FFIType.cstring],
187
+ returns: FFIType.void
188
+ },
189
+ removeTray: {
190
+ args: [FFIType.ptr],
191
+ returns: FFIType.void
192
+ },
193
+ setApplicationMenu: {
194
+ args: [FFIType.cstring, FFIType.function],
195
+ returns: FFIType.void
196
+ },
197
+ showContextMenu: {
198
+ args: [FFIType.cstring, FFIType.function],
199
+ returns: FFIType.void
200
+ },
201
+ moveToTrash: {
202
+ args: [FFIType.cstring],
203
+ returns: FFIType.bool
204
+ },
205
+ showItemInFolder: {
206
+ args: [FFIType.cstring],
207
+ returns: FFIType.void
208
+ },
209
+ openFileDialog: {
210
+ args: [
211
+ FFIType.cstring,
212
+ FFIType.cstring,
213
+ FFIType.int,
214
+ FFIType.int,
215
+ FFIType.int,
216
+ ],
217
+ returns: FFIType.cstring
218
+ },
219
+
220
+ // MacOS specific native utils
221
+ getNSWindowStyleMask: {
222
+ args: [
223
+ FFIType.bool,
224
+ FFIType.bool,
225
+ FFIType.bool,
226
+ FFIType.bool,
227
+ FFIType.bool,
228
+ FFIType.bool,
229
+ FFIType.bool,
230
+ FFIType.bool,
231
+ FFIType.bool,
232
+ FFIType.bool,
233
+ FFIType.bool,
234
+ FFIType.bool,
235
+ ],
236
+ returns: FFIType.u32
237
+ },
238
+ // JSCallback utils for native code to use
239
+ setJSUtils: {
240
+ args: [
241
+ FFIType.function, // get Mimetype from url/filename
242
+ FFIType.function, // get html property from webview
243
+ ],
244
+ returns: FFIType.void
245
+ },
246
+ killApp: {
247
+ args: [],
248
+ returns: FFIType.void
249
+ },
250
+ testFFI2: {
251
+ args: [FFIType.function],
252
+ returns: FFIType.void
253
+ },
254
+ // FFIFn: {
255
+ // args: [],
256
+ // returns: FFIType.void
257
+ // },
258
+ });
259
+ } catch (err) {
260
+ console.log('FATAL Error opening native FFI:', err.message);
261
+ console.log('This may be due to:');
262
+ console.log(' - Missing libNativeWrapper.dll/so/dylib');
263
+ console.log(' - Architecture mismatch (ARM64 vs x64)');
264
+ console.log(' - Missing WebView2 or CEF dependencies');
265
+ if (suffix === 'so') {
266
+ console.log(' - Missing system libraries (try: ldd ./libNativeWrapper.so)');
267
+ }
268
+ console.log('Check that the build process completed successfully for your architecture.');
269
+ process.exit();
270
+ }
271
+ })();
272
+
273
+ const callbacks = [];
274
+
275
+ // NOTE: Bun seems to hit limits on args or arg types. eg: trying to send 12 bools results
276
+ // in only about 8 going through then params after that. I think it may be similar to
277
+ // a zig bug I ran into last year. So check number of args in a signature when alignment issues occur.
278
+
279
+ // TODO XX: maybe this should actually be inside BrowserWindow and BrowserView as static methods
280
+ export const ffi = {
281
+ request: {
282
+ createWindow: (params: {
283
+ id: number,
284
+ url: string | null,
285
+ title: string,
286
+ frame: {
287
+ width: number,
288
+ height: number,
289
+ x: number,
290
+ y: number,
291
+ },
292
+ styleMask: {
293
+ Borderless: boolean,
294
+ Titled: boolean,
295
+ Closable: boolean,
296
+ Miniaturizable: boolean,
297
+ Resizable: boolean,
298
+ UnifiedTitleAndToolbar: boolean,
299
+ FullScreen: boolean,
300
+ FullSizeContentView: boolean,
301
+ UtilityWindow: boolean,
302
+ DocModalWindow: boolean,
303
+ NonactivatingPanel: boolean,
304
+ HUDWindow: boolean,
305
+ },
306
+ titleBarStyle: string,
307
+ }): FFIType.ptr => {
308
+ const {id, url, title, frame: {x, y, width, height}, styleMask: {
309
+ Borderless,
310
+ Titled,
311
+ Closable,
312
+ Miniaturizable,
313
+ Resizable,
314
+ UnifiedTitleAndToolbar,
315
+ FullScreen,
316
+ FullSizeContentView,
317
+ UtilityWindow,
318
+ DocModalWindow,
319
+ NonactivatingPanel,
320
+ HUDWindow
321
+ },
322
+ titleBarStyle} = params
323
+
324
+ const styleMask = native.symbols.getNSWindowStyleMask(
325
+ Borderless,
326
+ Titled,
327
+ Closable,
328
+ Miniaturizable,
329
+ Resizable,
330
+ UnifiedTitleAndToolbar,
331
+ FullScreen,
332
+ FullSizeContentView,
333
+ UtilityWindow,
334
+ DocModalWindow,
335
+ NonactivatingPanel,
336
+ HUDWindow
337
+ )
338
+
339
+ const windowPtr = native.symbols.createWindowWithFrameAndStyleFromWorker(
340
+ id,
341
+ // frame
342
+ x, y, width, height,
343
+ styleMask,
344
+ // style
345
+ toCString(titleBarStyle),
346
+ // callbacks
347
+ windowCloseCallback,
348
+ windowMoveCallback,
349
+ windowResizeCallback,
350
+ );
351
+
352
+
353
+ if (!windowPtr) {
354
+ throw "Failed to create window"
355
+ }
356
+
357
+ native.symbols.setNSWindowTitle(windowPtr, toCString(title));
358
+ // setTimeout(() => {
359
+ // console.log('calling makeNSWindowKeyAndOrderFront', windowPtr)
360
+ native.symbols.makeNSWindowKeyAndOrderFront(windowPtr);
361
+ // }, 1000)
362
+
363
+ return windowPtr;
364
+ },
365
+ setTitle: (params: {winId: number, title: string}) => {
366
+ const {winId, title} = params;
367
+ const windowPtr = BrowserWindow.getById(winId)?.ptr;
368
+
369
+
370
+ if (!windowPtr) {
371
+ throw `Can't add webview to window. window no longer exists`;
372
+ }
373
+
374
+ native.symbols.setNSWindowTitle(windowPtr, toCString(title));
375
+ },
376
+
377
+ closeWindow: (params: {winId: number}) => {
378
+ const {winId} = params;
379
+ const windowPtr = BrowserWindow.getById(winId)?.ptr;
380
+
381
+ if (!windowPtr) {
382
+ throw `Can't close window. Window no longer exists`;
383
+ }
384
+
385
+ native.symbols.closeNSWindow(windowPtr);
386
+ // Note: Cleanup of BrowserWindowMap happens in the windowCloseCallback
387
+ },
388
+
389
+ focusWindow: (params: {winId: number}) => {
390
+ const {winId} = params;
391
+ const windowPtr = BrowserWindow.getById(winId)?.ptr;
392
+
393
+ if (!windowPtr) {
394
+ throw `Can't focus window. Window no longer exists`;
395
+ }
396
+
397
+ native.symbols.makeNSWindowKeyAndOrderFront(windowPtr);
398
+ },
399
+
400
+ createWebview: (params: {
401
+ id: number;
402
+ windowId: number;
403
+ renderer: "cef" | "native";
404
+ rpcPort: number;
405
+ secretKey: string;
406
+ hostWebviewId: number | null;
407
+ pipePrefix: string;
408
+ url: string | null;
409
+ html: string | null;
410
+ partition: string | null;
411
+ preload: string | null;
412
+ frame: {
413
+ x: number;
414
+ y: number;
415
+ width: number;
416
+ height: number;
417
+ };
418
+ autoResize: boolean;
419
+ navigationRules: string | null;
420
+ }): FFIType.ptr => {
421
+
422
+ const { id,
423
+ windowId,
424
+ renderer,
425
+ rpcPort,
426
+ secretKey,
427
+ // hostWebviewId: number | null;
428
+ // pipePrefix: string;
429
+ url,
430
+ // html: string | null;
431
+ partition,
432
+ preload,
433
+ frame: {
434
+ x,
435
+ y,
436
+ width,
437
+ height,
438
+ },
439
+ autoResize} = params
440
+
441
+ const windowPtr = BrowserWindow.getById(windowId)?.ptr;
442
+
443
+
444
+ if (!windowPtr) {
445
+ throw `Can't add webview to window. window no longer exists`;
446
+ }
447
+
448
+ const electrobunPreload = `
449
+ window.__electrobunWebviewId = ${id};
450
+ window.__electrobunWindowId = ${windowId};
451
+ window.__electrobunRpcSocketPort = ${rpcPort};
452
+ window.__electrobunInternalBridge = window.webkit?.messageHandlers?.internalBridge || window.internalBridge || window.chrome?.webview?.hostObjects?.internalBridge;
453
+ window.__electrobunBunBridge = window.webkit?.messageHandlers?.bunBridge || window.bunBridge || window.chrome?.webview?.hostObjects?.bunBridge;
454
+ (async () => {
455
+
456
+ function base64ToUint8Array(base64) {
457
+ return new Uint8Array(atob(base64).split('').map(char => char.charCodeAt(0)));
458
+ }
459
+
460
+ function uint8ArrayToBase64(uint8Array) {
461
+ let binary = '';
462
+ for (let i = 0; i < uint8Array.length; i++) {
463
+ binary += String.fromCharCode(uint8Array[i]);
464
+ }
465
+ return btoa(binary);
466
+ }
467
+ const generateKeyFromText = async (rawKey) => {
468
+ return await window.crypto.subtle.importKey(
469
+ 'raw', // Key format
470
+ rawKey, // Key data
471
+ { name: 'AES-GCM' }, // Algorithm details
472
+ true, // Extractable (set to false for better security)
473
+ ['encrypt', 'decrypt'] // Key usages
474
+ );
475
+ };
476
+ const secretKey = await generateKeyFromText(new Uint8Array([${secretKey}]));
477
+
478
+ const encryptString = async (plaintext) => {
479
+ const encoder = new TextEncoder();
480
+ const encodedText = encoder.encode(plaintext);
481
+ const iv = window.crypto.getRandomValues(new Uint8Array(12)); // Initialization vector (12 bytes)
482
+ const encryptedBuffer = await window.crypto.subtle.encrypt(
483
+ {
484
+ name: "AES-GCM",
485
+ iv: iv,
486
+ },
487
+ secretKey,
488
+ encodedText
489
+ );
490
+
491
+
492
+ // Split the tag (last 16 bytes) from the ciphertext
493
+ const encryptedData = new Uint8Array(encryptedBuffer.slice(0, -16));
494
+ const tag = new Uint8Array(encryptedBuffer.slice(-16));
495
+
496
+ return { encryptedData: uint8ArrayToBase64(encryptedData), iv: uint8ArrayToBase64(iv), tag: uint8ArrayToBase64(tag) };
497
+ };
498
+
499
+ // All args passed in as base64 strings
500
+ const decryptString = async (encryptedData, iv, tag) => {
501
+ encryptedData = base64ToUint8Array(encryptedData);
502
+ iv = base64ToUint8Array(iv);
503
+ tag = base64ToUint8Array(tag);
504
+ // Combine encrypted data and tag to match the format expected by SubtleCrypto
505
+ const combinedData = new Uint8Array(encryptedData.length + tag.length);
506
+ combinedData.set(encryptedData);
507
+ combinedData.set(tag, encryptedData.length);
508
+ const decryptedBuffer = await window.crypto.subtle.decrypt(
509
+ {
510
+ name: "AES-GCM",
511
+ iv: iv,
512
+ },
513
+ secretKey,
514
+ combinedData // Pass the combined data (ciphertext + tag)
515
+ );
516
+ const decoder = new TextDecoder();
517
+ return decoder.decode(decryptedBuffer);
518
+ };
519
+
520
+ window.__electrobun_encrypt = encryptString;
521
+ window.__electrobun_decrypt = decryptString;
522
+ })();
523
+ ` + `
524
+ function emitWebviewEvent (eventName, detail) {
525
+ // Note: There appears to be some race bug with Bun FFI where sites can
526
+ // init (like views://myview/index.html) so fast while the Bun FFI to load a url is still executing
527
+ // or something where the JSCallback that this postMessage fires is not available or busy or
528
+ // its memory is allocated to something else or something and the handler receives garbage data in Bun.
529
+ setTimeout(() => {
530
+ console.log('emitWebviewEvent', eventName, detail)
531
+ window.__electrobunInternalBridge?.postMessage(JSON.stringify({id: 'webviewEvent', type: 'message', payload: {id: window.__electrobunWebviewId, eventName, detail}}));
532
+ });
533
+ };
534
+
535
+ window.addEventListener('load', function(event) {
536
+ // Check if the current window is the top-level window
537
+ if (window === window.top) {
538
+ emitWebviewEvent('dom-ready', document.location.href);
539
+ }
540
+ });
541
+
542
+ window.addEventListener('popstate', function(event) {
543
+ emitWebviewEvent('did-navigate-in-page', window.location.href);
544
+ });
545
+
546
+ window.addEventListener('hashchange', function(event) {
547
+ emitWebviewEvent('did-navigate-in-page', window.location.href);
548
+ });
549
+
550
+ document.addEventListener('click', function(event) {
551
+ if ((event.metaKey || event.ctrlKey) && event.target.tagName === 'A') {
552
+ event.preventDefault();
553
+ event.stopPropagation();
554
+
555
+ // Get the href of the link
556
+ const url = event.target.href;
557
+
558
+ // Open the URL in a new window or tab
559
+ // Note: we already handle new windows in objc
560
+ window.open(url, '_blank');
561
+ }
562
+ }, true);
563
+
564
+ // prevent overscroll
565
+ document.addEventListener('DOMContentLoaded', () => {
566
+ var style = document.createElement('style');
567
+ style.type = 'text/css';
568
+ style.appendChild(document.createTextNode('html, body { overscroll-behavior: none; }'));
569
+ document.head.appendChild(style);
570
+ });
571
+
572
+ `
573
+ const customPreload = preload;
574
+
575
+ const webviewPtr = native.symbols.initWebview(
576
+ id,
577
+ windowPtr,
578
+ toCString(renderer),
579
+ toCString(url || ''),
580
+ x, y,
581
+ width, height,
582
+ autoResize,
583
+ toCString(partition || 'persist:default'),
584
+ webviewDecideNavigation,
585
+ webviewEventJSCallback,
586
+ bunBridgePostmessageHandler,
587
+ internalBridgeHandler,
588
+ toCString(electrobunPreload),
589
+ toCString(customPreload || ''),
590
+ )
591
+
592
+ if (!webviewPtr) {
593
+ throw "Failed to create webview"
594
+ }
595
+
596
+ return webviewPtr;
597
+ },
598
+
599
+ evaluateJavascriptWithNoCompletion: (params: {id: number; js: string}) => {
600
+ const {id, js} = params;
601
+ const webview = BrowserView.getById(id);
602
+
603
+ if (!webview?.ptr) {
604
+ return;
605
+ }
606
+
607
+ native.symbols.evaluateJavaScriptWithNoCompletion(webview.ptr, toCString(js))
608
+ },
609
+
610
+ createTray: (params: {
611
+ id: number;
612
+ title: string;
613
+ image: string;
614
+ template: boolean;
615
+ width: number;
616
+ height: number;
617
+ }): FFIType.ptr => {
618
+ const {
619
+ id,
620
+ title,
621
+ image,
622
+ template,
623
+ width,
624
+ height
625
+ } = params;
626
+
627
+ const trayPtr = native.symbols.createTray(
628
+ id,
629
+ toCString(title),
630
+ toCString(image),
631
+ template,
632
+ width,
633
+ height,
634
+ trayItemHandler,
635
+ );
636
+
637
+ if (!trayPtr) {
638
+ throw 'Failed to create tray';
639
+ }
640
+
641
+ return trayPtr;
642
+ },
643
+ setTrayTitle: (params: {
644
+ id: number,
645
+ title: string,
646
+ }): void => {
647
+ const {
648
+ id,
649
+ title
650
+ } = params;
651
+
652
+ const tray = Tray.getById(id);
653
+
654
+ native.symbols.setTrayTitle(
655
+ tray.ptr,
656
+ toCString(title)
657
+ );
658
+ },
659
+ setTrayImage: (params: {
660
+ id: number,
661
+ image: string,
662
+ }): void => {
663
+ const {
664
+ id,
665
+ image
666
+ } = params;
667
+
668
+ const tray = Tray.getById(id);
669
+
670
+ native.symbols.setTrayImage(
671
+ tray.ptr,
672
+ toCString(image)
673
+ );
674
+ },
675
+ setTrayMenu: (params: {
676
+ id: number,
677
+ // json string of config
678
+ menuConfig: string,
679
+ }): void => {
680
+ const {
681
+ id,
682
+ menuConfig
683
+ } = params;
684
+
685
+ const tray = Tray.getById(id);
686
+ console.log('native.symbols.setTrayMenu', tray.ptr, menuConfig)
687
+ native.symbols.setTrayMenu(
688
+ tray.ptr,
689
+ toCString(menuConfig)
690
+ );
691
+ },
692
+
693
+ removeTray: (params: { id: number }): void => {
694
+ const { id } = params;
695
+ console.log('removeTray called with id:', id);
696
+ const tray = Tray.getById(id);
697
+ console.log('Found tray:', tray);
698
+
699
+ if (!tray) {
700
+ throw `Can't remove tray. Tray no longer exists`;
701
+ }
702
+
703
+ console.log('Calling native.symbols.removeTray with ptr:', tray.ptr);
704
+ native.symbols.removeTray(tray.ptr);
705
+ console.log('Native removeTray called successfully');
706
+ // The Tray class will handle removing from TrayMap
707
+ },
708
+ setApplicationMenu: (params: {menuConfig: string}): void => {
709
+ const {
710
+ menuConfig
711
+ } = params;
712
+
713
+ native.symbols.setApplicationMenu(
714
+ toCString(menuConfig),
715
+ applicationMenuHandler
716
+ );
717
+ },
718
+ showContextMenu: (params: {menuConfig: string}): void => {
719
+ const {
720
+ menuConfig
721
+ } = params;
722
+
723
+ native.symbols.showContextMenu(
724
+ toCString(menuConfig),
725
+ contextMenuHandler
726
+ );
727
+ },
728
+ moveToTrash: (params: {path: string}): boolean => {
729
+ const {
730
+ path
731
+ } = params;
732
+
733
+ return native.symbols.moveToTrash(toCString(path));
734
+ },
735
+ showItemInFolder: (params: {path: string}): void => {
736
+ const {
737
+ path
738
+ } = params;
739
+
740
+ native.symbols.showItemInFolder(toCString(path));
741
+ },
742
+ openFileDialog: (params: {startingFolder: string, allowedFileTypes: string, canChooseFiles: boolean, canChooseDirectory: boolean, allowsMultipleSelection: boolean}): string => {
743
+ const {
744
+ startingFolder,
745
+ allowedFileTypes,
746
+ canChooseFiles,
747
+ canChooseDirectory,
748
+ allowsMultipleSelection,
749
+ } = params;
750
+ const filePath = native.symbols.openFileDialog(toCString(startingFolder), toCString(allowedFileTypes), canChooseFiles, canChooseDirectory, allowsMultipleSelection);
751
+
752
+ return filePath.toString();
753
+ },
754
+
755
+ // ffifunc: (params: {}): void => {
756
+ // const {
757
+
758
+ // } = params;
759
+
760
+ // native.symbols.ffifunc(
761
+
762
+ // );
763
+ // },
764
+
765
+ }
766
+ }
767
+
768
+ // Worker management. Move to a different file
769
+ process.on('uncaughtException', (err) => {
770
+ console.error('Uncaught exception in worker:', err);
771
+ // Since the main js event loop is blocked by the native event loop
772
+ // we use FFI to dispatch a kill command to main
773
+ native.symbols.killApp();
774
+ });
775
+
776
+ process.on('unhandledRejection', (reason, promise) => {
777
+ console.error('Unhandled rejection in worker:', reason);
778
+ });
779
+
780
+ process.on('SIGINT', () => {
781
+ console.log('[electrobun] Received SIGINT, calling killApp() for graceful shutdown...');
782
+ native.symbols.killApp();
783
+ });
784
+
785
+ process.on('SIGTERM', () => {
786
+ console.log('[electrobun] Received SIGTERM, calling killApp() for graceful shutdown...');
787
+ native.symbols.killApp();
788
+ });
789
+
790
+
791
+
792
+
793
+ // const testCallback = new JSCallback(
794
+ // (windowId, x, y) => {
795
+ // console.log(`TEST FFI Callback reffed GLOBALLY in js`);
796
+ // // Your window move handler implementation
797
+ // },
798
+ // {
799
+ // args: [],
800
+ // returns: "void",
801
+ // threadsafe: true,
802
+
803
+ // }
804
+ // );
805
+
806
+
807
+ const windowCloseCallback = new JSCallback(
808
+ (id) => {
809
+ const handler = electrobunEventEmitter.events.window.close;
810
+ const event = handler({
811
+ id,
812
+ });
813
+
814
+ let result;
815
+ // global event
816
+ result = electrobunEventEmitter.emitEvent(event);
817
+
818
+ result = electrobunEventEmitter.emitEvent(event, id);
819
+ },
820
+ {
821
+ args: ["u32"],
822
+ returns: "void",
823
+ threadsafe: true,
824
+ }
825
+ );
826
+
827
+ const windowMoveCallback = new JSCallback(
828
+ (id, x, y) => {
829
+ const handler = electrobunEventEmitter.events.window.move;
830
+ const event = handler({
831
+ id,
832
+ x,
833
+ y,
834
+ });
835
+
836
+ let result;
837
+ // global event
838
+ result = electrobunEventEmitter.emitEvent(event);
839
+
840
+ result = electrobunEventEmitter.emitEvent(event, id);
841
+ },
842
+ {
843
+ args: ["u32", "f64", "f64"],
844
+ returns: "void",
845
+ threadsafe: true,
846
+ }
847
+ );
848
+
849
+ const windowResizeCallback = new JSCallback(
850
+ (id, x, y, width, height) => {
851
+ const handler = electrobunEventEmitter.events.window.resize;
852
+ const event = handler({
853
+ id,
854
+ x,
855
+ y,
856
+ width,
857
+ height,
858
+ });
859
+
860
+ let result;
861
+ // global event
862
+ result = electrobunEventEmitter.emitEvent(event);
863
+
864
+ result = electrobunEventEmitter.emitEvent(event, id);
865
+ },
866
+ {
867
+ args: ["u32", "f64", "f64", "f64", "f64"],
868
+ returns: "void",
869
+ threadsafe: true,
870
+ }
871
+ );
872
+
873
+ const getMimeType = new JSCallback((filePath) => {
874
+ const _filePath = new CString(filePath).toString();
875
+ const mimeType = Bun.file(_filePath).type;// || "application/octet-stream";
876
+
877
+ // For this usecase we generally don't want the charset included in the mimetype
878
+ // otherwise it can break. eg: for html with text/javascript;charset=utf-8 browsers
879
+ // will tend to render the code/text instead of interpreting the html.
880
+
881
+ return toCString(mimeType.split(';')[0]);
882
+ }, {
883
+ args: [FFIType.cstring],
884
+ returns: FFIType.cstring,
885
+ // threadsafe: true
886
+ });
887
+
888
+ const getHTMLForWebviewSync = new JSCallback((webviewId) => {
889
+ const webview = BrowserView.getById(webviewId);
890
+
891
+ return toCString(webview?.html || '');
892
+ }, {
893
+ args: [FFIType.cstring],
894
+ returns: FFIType.cstring,
895
+ // threadsafe: true
896
+ });
897
+
898
+
899
+ native.symbols.setJSUtils(getMimeType, getHTMLForWebviewSync);
900
+
901
+ // TODO XX: revisit this as integrated into the will-navigate handler
902
+ const webviewDecideNavigation = new JSCallback((webviewId, url) => {
903
+ console.log('webviewDecideNavigation', webviewId, new CString(url))
904
+ return true;
905
+ }, {
906
+ args: [FFIType.u32, FFIType.cstring],
907
+ // NOTE: In Objc true is YES which is so dumb, but that doesn't work with Bun's FFIType.bool
908
+ // in JSCallbacks right now (it always infers false) so to make this cross platform we have to use
909
+ // FFIType.u32 and uint32_t and then just treat it as a boolean in code.
910
+ returns: FFIType.u32,
911
+ threadsafe: true
912
+ });
913
+
914
+
915
+ const webviewEventHandler = (id, eventName, detail) => {
916
+ const webview = BrowserView.getById(id);
917
+ if (webview.hostWebviewId) {
918
+ // This is a webviewtag so we should send the event into the parent as well
919
+ // TODO XX: escape event name and detail to remove `
920
+ const js = `document.querySelector('#electrobun-webview-${id}').emit(\`${eventName}\`, \`${detail}\`);`
921
+
922
+ native.symbols.evaluateJavaScriptWithNoCompletion(webview.ptr, toCString(js))
923
+ }
924
+
925
+ const eventMap = {
926
+ "will-navigate": "willNavigate",
927
+ "did-navigate": "didNavigate",
928
+ "did-navigate-in-page": "didNavigateInPage",
929
+ "did-commit-navigation": "didCommitNavigation",
930
+ "dom-ready": "domReady",
931
+ "new-window-open": "newWindowOpen",
932
+ };
933
+
934
+ // todo: the events map should use the same hyphenated names instead of camelCase
935
+ const handler =
936
+ electrobunEventEmitter.events.webview[eventMap[eventName]];
937
+
938
+ if (!handler) {
939
+
940
+ return { success: false };
941
+ }
942
+
943
+ const event = handler({
944
+ id,
945
+ detail,
946
+ });
947
+
948
+ let result;
949
+ // global event
950
+ result = electrobunEventEmitter.emitEvent(event);
951
+ result = electrobunEventEmitter.emitEvent(event, id);
952
+ }
953
+
954
+ const webviewEventJSCallback = new JSCallback((id, _eventName, _detail) => {
955
+ const eventName = new CString(_eventName);
956
+ const detail = new CString(_detail);
957
+
958
+ webviewEventHandler(id, eventName, detail);
959
+ }, {
960
+ args: [FFIType.u32, FFIType.cstring],
961
+ returns: FFIType.void,
962
+ threadsafe: true
963
+ });
964
+
965
+
966
+
967
+ const bunBridgePostmessageHandler = new JSCallback((id, msg) => {
968
+ try {
969
+ const msgStr = new CString(msg);
970
+
971
+ if (!msgStr.length) {
972
+ return;
973
+ }
974
+ const msgJson = JSON.parse(msgStr);
975
+
976
+ const webview = BrowserView.getById(id);
977
+
978
+ webview.rpcHandler?.(msgJson).then(result => {
979
+ }).catch(err => console.log('error in rpchandler', err))
980
+
981
+ } catch (err) {
982
+ console.error('error sending message to bun: ', err)
983
+ console.error('msgString: ', new CString(msg));
984
+ }
985
+
986
+
987
+ }, {
988
+ args: [FFIType.u32, FFIType.cstring],
989
+ returns: FFIType.void,
990
+ threadsafe: true
991
+ });
992
+
993
+ // internalRPC (bun <-> browser internal stuff)
994
+ // BrowserView.rpc (user defined bun <-> browser rpc unique to each webview)
995
+ // nativeRPC (internal bun <-> native rpc)
996
+
997
+
998
+ const internalBridgeHandler = new JSCallback((id, msg) => {
999
+ try {
1000
+ const batchMessage = new CString(msg);
1001
+ const jsonBatch = JSON.parse(batchMessage);
1002
+
1003
+ if (jsonBatch.id === 'webviewEvent'){
1004
+ // Note: Some WebviewEvents from inside the webview are routed through here
1005
+ // Others call the JSCallback directly from native code.
1006
+ const {payload} = jsonBatch;
1007
+ webviewEventHandler(payload.id, payload.eventName, payload.detail);
1008
+ return;
1009
+ }
1010
+
1011
+
1012
+ jsonBatch.forEach((msgStr) => {
1013
+ // if (!msgStr.length) {
1014
+ // console.error('WEBVIEW EVENT SENT TO WEBVIEW TAG BRIDGE HANDLER?', )
1015
+ // return;
1016
+ // }
1017
+ const msgJson = JSON.parse(msgStr);
1018
+
1019
+ if (msgJson.type === 'message') {
1020
+ const handler = internalRpcHandlers.message[msgJson.id];
1021
+ handler(msgJson.payload);
1022
+ } else if(msgJson.type === 'request') {
1023
+ const hostWebview = BrowserView.getById(msgJson.hostWebviewId);
1024
+ // const targetWebview = BrowserView.getById(msgJson.params.params.hostWebviewId);
1025
+ const handler = internalRpcHandlers.request[msgJson.method];
1026
+
1027
+
1028
+ const payload = handler(msgJson.params);
1029
+
1030
+
1031
+ const resultObj = {
1032
+ type: 'response',
1033
+ id: msgJson.id,
1034
+ success: true,
1035
+ payload,
1036
+ }
1037
+
1038
+ if (!hostWebview) {
1039
+ console.log('--->>> internal request in bun: NO HOST WEBVIEW FOUND');
1040
+ return
1041
+ }
1042
+
1043
+ hostWebview.sendInternalMessageViaExecute(resultObj);
1044
+ }
1045
+ });
1046
+
1047
+ } catch (err) {
1048
+ console.error('error in internalBridgeHandler: ', err)
1049
+ // console.log('msgStr: ', id, new CString(msg));
1050
+ }
1051
+
1052
+
1053
+ }, {
1054
+ args: [FFIType.u32, FFIType.cstring],
1055
+ returns: FFIType.void,
1056
+ threadsafe: true
1057
+ });
1058
+
1059
+ const trayItemHandler = new JSCallback((id, action) => {
1060
+ const event = electrobunEventEmitter.events.tray.trayClicked({
1061
+ id,
1062
+ action: new CString(action),
1063
+ });
1064
+
1065
+ let result;
1066
+ // global event
1067
+ result = electrobunEventEmitter.emitEvent(event);
1068
+ result = electrobunEventEmitter.emitEvent(event, id);
1069
+ }, {
1070
+ args: [FFIType.u32, FFIType.cstring],
1071
+ returns: FFIType.void,
1072
+ threadsafe: true,
1073
+ })
1074
+
1075
+
1076
+ const applicationMenuHandler = new JSCallback((id, action) => {
1077
+ const event = electrobunEventEmitter.events.app.applicationMenuClicked({
1078
+ id,
1079
+ action: new CString(action),
1080
+ });
1081
+
1082
+ // global event
1083
+ electrobunEventEmitter.emitEvent(event);
1084
+ }, {
1085
+ args: [FFIType.u32, FFIType.cstring],
1086
+ returns: FFIType.void,
1087
+ threadsafe: true
1088
+ })
1089
+
1090
+ const contextMenuHandler = new JSCallback((id, action) => {
1091
+ const event = electrobunEventEmitter.events.app.contextMenuClicked({
1092
+ action: new CString(action),
1093
+ });
1094
+
1095
+ electrobunEventEmitter.emitEvent(event);
1096
+ }, {
1097
+ args: [FFIType.u32, FFIType.cstring],
1098
+ returns: FFIType.void,
1099
+ threadsafe: true
1100
+ })
1101
+
1102
+ // Note: When passed over FFI JS will GC the buffer/pointer. Make sure to use strdup() or something
1103
+ // on the c side to duplicate the string so objc/c++ gc can own it
1104
+ export function toCString(jsString: string, addNullTerminator: boolean = true): CString {
1105
+ let appendWith = '';
1106
+
1107
+ if (addNullTerminator && !jsString.endsWith('\0')) {
1108
+ appendWith = '\0';
1109
+ }
1110
+ const buff = Buffer.from(jsString + appendWith, 'utf8');
1111
+
1112
+ // @ts-ignore - This is valid in Bun
1113
+ return ptr(buff);
1114
+ }
1115
+
1116
+
1117
+
1118
+ export const internalRpcHandlers = {
1119
+ request: {
1120
+ // todo: this shouldn't be getting method, just params.
1121
+ webviewTagInit: (params:
1122
+ BrowserViewOptions & { windowId: number }
1123
+ ) => {
1124
+
1125
+ const {
1126
+ hostWebviewId,
1127
+ windowId,
1128
+ renderer,
1129
+ html,
1130
+ preload,
1131
+ partition,
1132
+ frame,
1133
+ navigationRules,
1134
+ } = params;
1135
+
1136
+ const url = !params.url && !html ? "https://electrobun.dev" : params.url;
1137
+
1138
+ const webviewForTag = new BrowserView({
1139
+ url,
1140
+ html,
1141
+ preload,
1142
+ partition,
1143
+ frame,
1144
+ hostWebviewId,
1145
+ autoResize: false,
1146
+ windowId,
1147
+ renderer,//: "cef",
1148
+ navigationRules,
1149
+ });
1150
+
1151
+ return webviewForTag.id;
1152
+ },
1153
+ webviewTagCanGoBack: (params) => {
1154
+ const {id} = params;
1155
+ const webviewPtr = BrowserView.getById(id)?.ptr;
1156
+ if (!webviewPtr) {
1157
+ console.error('no webview ptr')
1158
+ return false;
1159
+ }
1160
+
1161
+ return native.symbols.webviewCanGoBack(webviewPtr);
1162
+ },
1163
+ webviewTagCanGoForward: (params) => {
1164
+ const {id} = params;
1165
+ const webviewPtr = BrowserView.getById(id)?.ptr;
1166
+ if (!webviewPtr) {
1167
+ console.error('no webview ptr')
1168
+ return false;
1169
+ }
1170
+
1171
+ return native.symbols.webviewCanGoForward(webviewPtr);
1172
+ },
1173
+ webviewTagCallAsyncJavaScript: (params) => {
1174
+ console.log('-----------+ request: ', 'webviewTagCallAsyncJavaScript', params)
1175
+ // Not implemented - users can use RPC for JavaScript execution if needed
1176
+ }
1177
+ },
1178
+ message: {
1179
+ webviewTagResize: (params) => {
1180
+ const browserView = BrowserView.getById(params.id);
1181
+ const webviewPtr = browserView?.ptr;
1182
+
1183
+ if (!webviewPtr) {
1184
+ console.log('[Bun] ERROR: webviewTagResize - no webview ptr found for id:', params.id);
1185
+ return;
1186
+ }
1187
+
1188
+ const {x, y, width, height} = params.frame;
1189
+ native.symbols.resizeWebview(webviewPtr, x, y, width, height, toCString(params.masks))
1190
+ },
1191
+ webviewTagUpdateSrc: (params) => {
1192
+ const webview = BrowserView.getById(params.id);
1193
+ if (!webview || !webview.ptr) {
1194
+ console.error(`webviewTagUpdateSrc: BrowserView not found or has no ptr for id ${params.id}`);
1195
+ return;
1196
+ }
1197
+ native.symbols.loadURLInWebView(webview.ptr, toCString(params.url));
1198
+ },
1199
+ webviewTagUpdateHtml: (params) => {
1200
+ const webview = BrowserView.getById(params.id);
1201
+ if (!webview || !webview.ptr) {
1202
+ console.error(`webviewTagUpdateHtml: BrowserView not found or has no ptr for id ${params.id}`);
1203
+ return;
1204
+ }
1205
+ webview.loadHTML(params.html);
1206
+ webview.html = params.html;
1207
+ },
1208
+ webviewTagUpdatePreload: (params) => {
1209
+ const webview = BrowserView.getById(params.id);
1210
+ if (!webview || !webview.ptr) {
1211
+ console.error(`webviewTagUpdatePreload: BrowserView not found or has no ptr for id ${params.id}`);
1212
+ return;
1213
+ }
1214
+ native.symbols.updatePreloadScriptToWebView(webview.ptr, toCString('electrobun_custom_preload_script'), toCString(params.preload), true);
1215
+ },
1216
+ webviewTagGoBack: (params) => {
1217
+ const webview = BrowserView.getById(params.id);
1218
+ if (!webview || !webview.ptr) {
1219
+ console.error(`webviewTagGoBack: BrowserView not found or has no ptr for id ${params.id}`);
1220
+ return;
1221
+ }
1222
+ native.symbols.webviewGoBack(webview.ptr);
1223
+ },
1224
+ webviewTagGoForward: (params) => {
1225
+ const webview = BrowserView.getById(params.id);
1226
+ if (!webview || !webview.ptr) {
1227
+ console.error(`webviewTagGoForward: BrowserView not found or has no ptr for id ${params.id}`);
1228
+ return;
1229
+ }
1230
+ native.symbols.webviewGoForward(webview.ptr);
1231
+ },
1232
+ webviewTagReload: (params) => {
1233
+ const webview = BrowserView.getById(params.id);
1234
+ if (!webview || !webview.ptr) {
1235
+ console.error(`webviewTagReload: BrowserView not found or has no ptr for id ${params.id}`);
1236
+ return;
1237
+ }
1238
+ native.symbols.webviewReload(webview.ptr);
1239
+ },
1240
+ webviewTagRemove: (params) => {
1241
+ const webview = BrowserView.getById(params.id);
1242
+ if (!webview || !webview.ptr) {
1243
+ console.error(`webviewTagRemove: BrowserView not found or has no ptr for id ${params.id}`);
1244
+ return;
1245
+ }
1246
+ native.symbols.webviewRemove(webview.ptr);
1247
+ },
1248
+ startWindowMove: (params) => {
1249
+ const window = BrowserWindow.getById(params.id);
1250
+ native.symbols.startWindowMove(window.ptr);
1251
+ },
1252
+ stopWindowMove: (params) => {
1253
+ native.symbols.stopWindowMove();
1254
+ },
1255
+ webviewTagSetTransparent: (params) => {
1256
+ const webview = BrowserView.getById(params.id);
1257
+ if (!webview || !webview.ptr) {
1258
+ console.error(`webviewTagSetTransparent: BrowserView not found or has no ptr for id ${params.id}`);
1259
+ return;
1260
+ }
1261
+ native.symbols.webviewSetTransparent(webview.ptr, params.transparent);
1262
+ },
1263
+ webviewTagSetPassthrough: (params) => {
1264
+ const webview = BrowserView.getById(params.id);
1265
+ if (!webview || !webview.ptr) {
1266
+ console.error(`webviewTagSetPassthrough: BrowserView not found or has no ptr for id ${params.id}`);
1267
+ return;
1268
+ }
1269
+ native.symbols.webviewSetPassthrough(webview.ptr, params.enablePassthrough);
1270
+ },
1271
+ webviewTagSetHidden: (params) => {
1272
+ const webview = BrowserView.getById(params.id);
1273
+ if (!webview || !webview.ptr) {
1274
+ console.error(`webviewTagSetHidden: BrowserView not found or has no ptr for id ${params.id}`);
1275
+ return;
1276
+ }
1277
+ native.symbols.webviewSetHidden(webview.ptr, params.hidden);
1278
+ },
1279
+ webviewEvent: (params) => {
1280
+ console.log('-----------------+webviewEvent', params)
1281
+ },
1282
+ }
1283
+ };
1284
+
1285
+ // todo: consider renaming to TrayMenuItemConfig
1286
+ export type MenuItemConfig =
1287
+ | { type: "divider" | "separator" }
1288
+ | {
1289
+ type: "normal";
1290
+ label: string;
1291
+ tooltip?: string;
1292
+ action?: string;
1293
+ submenu?: Array<MenuItemConfig>;
1294
+ enabled?: boolean;
1295
+ checked?: boolean;
1296
+ hidden?: boolean;
1297
+ };
1298
+
1299
+ export type ApplicationMenuItemConfig =
1300
+ | { type: "divider" | "separator" }
1301
+ | {
1302
+ type?: "normal";
1303
+ label: string;
1304
+ tooltip?: string;
1305
+ action?: string;
1306
+ submenu?: Array<ApplicationMenuItemConfig>;
1307
+ enabled?: boolean;
1308
+ checked?: boolean;
1309
+ hidden?: boolean;
1310
+ accelerator?: string;
1311
+ }
1312
+ | {
1313
+ type?: "normal";
1314
+ label?: string;
1315
+ tooltip?: string;
1316
+ role?: string;
1317
+ submenu?: Array<ApplicationMenuItemConfig>;
1318
+ enabled?: boolean;
1319
+ checked?: boolean;
1320
+ hidden?: boolean;
1321
+ accelerator?: string;
1322
+ };