electrobun 0.0.2 → 0.0.4
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/.colab.json +1 -0
- package/README.md +14 -12
- package/bun.lockb +0 -0
- package/dist/bsdiff +0 -0
- package/dist/bspatch +0 -0
- package/dist/webview +0 -0
- package/docs-old/architecture.md +46 -0
- package/documentation/README.md +41 -0
- package/documentation/babel.config.js +3 -0
- package/documentation/blog/2024-08-20-electrobun.md +22 -0
- package/documentation/blog/authors.yml +8 -0
- package/documentation/blog/tags.yml +0 -0
- package/documentation/docs/apis/Application Icons.md +9 -0
- package/documentation/docs/apis/Bundled Assets.md +95 -0
- package/documentation/docs/apis/browser/DraggableRegions.md +36 -0
- package/documentation/docs/apis/browser/Electrobun Webview Tag.md +200 -0
- package/documentation/docs/apis/browser/Electroview Class.md +158 -0
- package/documentation/docs/apis/browser/GlobalProperties.md +11 -0
- package/documentation/docs/apis/browser/index.md +25 -0
- package/documentation/docs/apis/bun/ApplicationMenu.md +141 -0
- package/documentation/docs/apis/bun/BrowserView.md +513 -0
- package/documentation/docs/apis/bun/BrowserWindow.md +423 -0
- package/documentation/docs/apis/bun/ContextMenu.md +50 -0
- package/documentation/docs/apis/bun/Events.md +50 -0
- package/documentation/docs/apis/bun/PATHS.md +17 -0
- package/documentation/docs/apis/bun/Tray.md +115 -0
- package/documentation/docs/apis/bun/Updater.md +74 -0
- package/documentation/docs/apis/bun/Utils.md +51 -0
- package/documentation/docs/apis/bun/index.md +26 -0
- package/documentation/docs/apis/cli/Electrobun.config.md +97 -0
- package/documentation/docs/apis/cli/cli args.md +76 -0
- package/documentation/docs/guides/Architecture/Events.md +19 -0
- package/documentation/docs/guides/Architecture/IPC and Isolation.md +20 -0
- package/documentation/docs/guides/Architecture/Overview.md +140 -0
- package/documentation/docs/guides/Architecture/Updates.md +7 -0
- package/documentation/docs/guides/Architecture/Webview Tag.md +5 -0
- package/documentation/docs/guides/Compatability.md +8 -0
- package/documentation/docs/guides/Getting Started/Creating UI.md +147 -0
- package/documentation/docs/guides/Getting Started/Distributing.md +116 -0
- package/documentation/docs/guides/Getting Started/Getting Started.md +7 -0
- package/documentation/docs/guides/Getting Started/Hello World.md +93 -0
- package/documentation/docs/guides/Getting Started/What is Electrobun.md +39 -0
- package/documentation/docs/guides/Guides/Build UI with React +0 -0
- package/documentation/docs/guides/Guides/Build UI with Solidjs +0 -0
- package/documentation/docs/guides/Guides/Build a Web Browser +0 -0
- package/documentation/docs/guides/Guides/Bun <-> Browser RPC +0 -0
- package/documentation/docs/guides/Guides/Using Tailwind +0 -0
- package/documentation/docusaurus.config.ts +153 -0
- package/documentation/package-lock.json +14530 -0
- package/documentation/package.json +47 -0
- package/documentation/sidebars.ts +32 -0
- package/documentation/src/components/HomepageFeatures/index.tsx +70 -0
- package/documentation/src/components/HomepageFeatures/styles.module.css +11 -0
- package/documentation/src/css/custom.css +30 -0
- package/documentation/src/pages/index.module.css +23 -0
- package/documentation/src/pages/index.tsx +137 -0
- package/documentation/static/.nojekyll +0 -0
- package/documentation/static/img/electrobun-logo-256.png +0 -0
- package/documentation/static/img/electrobun-logo-32.png +0 -0
- package/documentation/tsconfig.json +7 -0
- package/package.json +11 -6
- package/src/browser/index.ts +7 -2
- package/src/browser/webviewtag.ts +149 -17
- package/src/bun/core/BrowserView.ts +19 -2
- package/src/bun/proc/zig.ts +1 -0
- package/src/cli/build/electrobun +0 -0
- package/src/cli/index.ts +3 -1
- package/src/extractor/zig-out/bin/extractor +0 -0
- package/src/launcher/zig-out/bin/launcher +0 -0
- package/docs/architecture.md +0 -84
- /package/{docs → docs-old}/api/bun-api.md +0 -0
- /package/{docs → docs-old}/api/view-api.md +0 -0
- /package/{docs → docs-old}/electrobun-config.md +0 -0
- /package/{docs → docs-old}/getting-started.md +0 -0
- /package/{docs → docs-old}/node_modules/.cache/webpack/client-development-en/0.pack +0 -0
- /package/{docs → docs-old}/node_modules/.cache/webpack/client-development-en/index.pack +0 -0
|
@@ -4,6 +4,8 @@ type WebviewEventTypes =
|
|
|
4
4
|
| "did-commit-navigation"
|
|
5
5
|
| "dom-ready";
|
|
6
6
|
|
|
7
|
+
type Rect = { x: number; y: number; width: number; height: number };
|
|
8
|
+
|
|
7
9
|
const ConfigureWebviewTags = (
|
|
8
10
|
enableWebviewTags: boolean,
|
|
9
11
|
zigRpc: (params: any) => any,
|
|
@@ -24,6 +26,10 @@ const ConfigureWebviewTags = (
|
|
|
24
26
|
zigRpc: any;
|
|
25
27
|
syncRpc: any;
|
|
26
28
|
|
|
29
|
+
// querySelectors for elements that you want to appear
|
|
30
|
+
// in front of the webview.
|
|
31
|
+
maskSelectors: Set<string> = new Set();
|
|
32
|
+
|
|
27
33
|
// observers
|
|
28
34
|
resizeObserver?: ResizeObserver;
|
|
29
35
|
// intersectionObserver?: IntersectionObserver;
|
|
@@ -39,12 +45,16 @@ const ConfigureWebviewTags = (
|
|
|
39
45
|
height: 0,
|
|
40
46
|
};
|
|
41
47
|
|
|
48
|
+
lastMasksJSON: string = "";
|
|
49
|
+
lastMasks: Rect[] = [];
|
|
50
|
+
|
|
42
51
|
transparent: boolean = false;
|
|
43
52
|
passthroughEnabled: boolean = false;
|
|
44
53
|
hidden: boolean = false;
|
|
45
54
|
delegateMode: boolean = false;
|
|
46
55
|
hiddenMirrorMode: boolean = false;
|
|
47
56
|
wasZeroRect: boolean = false;
|
|
57
|
+
isMirroring: boolean = false;
|
|
48
58
|
|
|
49
59
|
partition: string | null = null;
|
|
50
60
|
|
|
@@ -59,6 +69,16 @@ const ConfigureWebviewTags = (
|
|
|
59
69
|
});
|
|
60
70
|
}
|
|
61
71
|
|
|
72
|
+
addMaskSelector(selector: string) {
|
|
73
|
+
this.maskSelectors.add(selector);
|
|
74
|
+
this.syncDimensions();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
removeMaskSelector(selector: string) {
|
|
78
|
+
this.maskSelectors.delete(selector);
|
|
79
|
+
this.syncDimensions();
|
|
80
|
+
}
|
|
81
|
+
|
|
62
82
|
initWebview() {
|
|
63
83
|
const rect = this.getBoundingClientRect();
|
|
64
84
|
this.lastRect = rect;
|
|
@@ -206,6 +226,10 @@ const ConfigureWebviewTags = (
|
|
|
206
226
|
// know that they're chaning something in order to eliminate the lag that the
|
|
207
227
|
// catch all loop will catch
|
|
208
228
|
syncDimensions(force: boolean = false) {
|
|
229
|
+
if (!force && this.hidden) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
209
233
|
const rect = this.getBoundingClientRect();
|
|
210
234
|
const { x, y, width, height } =
|
|
211
235
|
this.adjustDimensionsForHiddenMirrorMode(rect);
|
|
@@ -220,19 +244,45 @@ const ConfigureWebviewTags = (
|
|
|
220
244
|
return;
|
|
221
245
|
}
|
|
222
246
|
|
|
247
|
+
const masks: Rect[] = [];
|
|
248
|
+
this.maskSelectors.forEach((selector) => {
|
|
249
|
+
const els = document.querySelectorAll(selector);
|
|
250
|
+
|
|
251
|
+
for (let i = 0; i < els.length; i++) {
|
|
252
|
+
const el = els[i];
|
|
253
|
+
|
|
254
|
+
if (el) {
|
|
255
|
+
const maskRect = el.getBoundingClientRect();
|
|
256
|
+
|
|
257
|
+
masks.push({
|
|
258
|
+
// reposition the bounding rect to be relative to the webview rect
|
|
259
|
+
// so objc can apply the mask correctly and handle the actual overlap
|
|
260
|
+
x: maskRect.x - x,
|
|
261
|
+
y: maskRect.y - y,
|
|
262
|
+
width: maskRect.width,
|
|
263
|
+
height: maskRect.height,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// store jsonStringified last masks value to compare
|
|
270
|
+
const masksJson = masks.length ? JSON.stringify(masks) : "";
|
|
271
|
+
|
|
223
272
|
if (
|
|
224
273
|
force ||
|
|
225
274
|
lastRect.x !== x ||
|
|
226
275
|
lastRect.y !== y ||
|
|
227
276
|
lastRect.width !== width ||
|
|
228
|
-
lastRect.height !== height
|
|
277
|
+
lastRect.height !== height ||
|
|
278
|
+
this.lastMasksJSON !== masksJson
|
|
229
279
|
) {
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
this.setPositionCheckLoop(true);
|
|
233
|
-
}
|
|
280
|
+
// let it know we're still accelerating
|
|
281
|
+
this.setPositionCheckLoop(true);
|
|
234
282
|
|
|
235
283
|
this.lastRect = rect;
|
|
284
|
+
this.lastMasks = masks;
|
|
285
|
+
this.lastMasksJSON = masksJson;
|
|
236
286
|
|
|
237
287
|
this.zigRpc.send.webviewTagResize({
|
|
238
288
|
id: this.webviewId,
|
|
@@ -242,6 +292,7 @@ const ConfigureWebviewTags = (
|
|
|
242
292
|
x: x,
|
|
243
293
|
y: y,
|
|
244
294
|
},
|
|
295
|
+
masks: masksJson,
|
|
245
296
|
});
|
|
246
297
|
}
|
|
247
298
|
|
|
@@ -265,13 +316,12 @@ const ConfigureWebviewTags = (
|
|
|
265
316
|
this.positionCheckLoopReset = undefined;
|
|
266
317
|
}
|
|
267
318
|
|
|
268
|
-
const delay = accelerate ?
|
|
319
|
+
const delay = accelerate ? 0 : 300;
|
|
269
320
|
|
|
270
321
|
if (accelerate) {
|
|
271
|
-
clearTimeout(this.positionCheckLoopReset);
|
|
272
322
|
this.positionCheckLoopReset = setTimeout(() => {
|
|
273
323
|
this.setPositionCheckLoop(false);
|
|
274
|
-
},
|
|
324
|
+
}, 2000);
|
|
275
325
|
}
|
|
276
326
|
// Note: Since there's not catch all way to listen for x/y changes
|
|
277
327
|
// we have a 400ms interval to check
|
|
@@ -288,6 +338,59 @@ const ConfigureWebviewTags = (
|
|
|
288
338
|
this.positionCheckLoop = setInterval(() => this.syncDimensions(), delay);
|
|
289
339
|
}
|
|
290
340
|
|
|
341
|
+
// The global document mousemove will fire even when the mouse is over
|
|
342
|
+
// an OOPIF that's layered above this host webview. The two edge cases we
|
|
343
|
+
// solve for are:
|
|
344
|
+
// 1. dragging an element on the host over or dropping on the webview anchor
|
|
345
|
+
// 2. clicking on an element that's "layered over" the OOPIF visually but really uses a
|
|
346
|
+
// mask to make a section transparent. We want the underlying overlay UI to be
|
|
347
|
+
// interactive not the OOPIF.
|
|
348
|
+
//
|
|
349
|
+
// Solution: Have mirroing on by default.
|
|
350
|
+
// 1. mouse move events don't fire during drag. So the OOPIF remains non-interactive
|
|
351
|
+
// and effectively passes through to the host's anchor element underneath letting you
|
|
352
|
+
// react to drag events on the host as needed.
|
|
353
|
+
// 2. Detect when the mouse is moving over the anchor and turn mirroring off to make
|
|
354
|
+
// it interactive. Unless the mouse is over a masked area in which case we want to
|
|
355
|
+
// keep it non-interactive and pass through to the host "overylay UI".
|
|
356
|
+
handleDocumentMouseMove(e: MouseEvent) {
|
|
357
|
+
if (this.hidden) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const isInBounds =
|
|
362
|
+
e.clientX >= this.lastRect.x &&
|
|
363
|
+
e.clientX <= this.lastRect.x + this.lastRect.width &&
|
|
364
|
+
e.clientY >= this.lastRect.y &&
|
|
365
|
+
e.clientY <= this.lastRect.y + this.lastRect.height;
|
|
366
|
+
|
|
367
|
+
if (isInBounds) {
|
|
368
|
+
const isInMaskBounds = this.lastMasks.find((mask) => {
|
|
369
|
+
// we send relative x/y to objc but here we need the clientX/Y
|
|
370
|
+
// to compare against. consider doing the opposite or storing both.
|
|
371
|
+
const clientX = this.lastRect.x + mask.x;
|
|
372
|
+
const clientY = this.lastRect.y + mask.y;
|
|
373
|
+
const isInMaskBounds =
|
|
374
|
+
e.clientX >= clientX &&
|
|
375
|
+
e.clientX <= clientX + mask.width &&
|
|
376
|
+
e.clientY <= clientY + mask.height &&
|
|
377
|
+
e.clientY >= clientY;
|
|
378
|
+
|
|
379
|
+
return isInMaskBounds;
|
|
380
|
+
});
|
|
381
|
+
if (isInMaskBounds) {
|
|
382
|
+
this.startMirroring();
|
|
383
|
+
} else {
|
|
384
|
+
this.stopMirroring();
|
|
385
|
+
}
|
|
386
|
+
} else {
|
|
387
|
+
this.startMirroring();
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
boundhandleDocumentMouseMove = (e: MouseEvent) =>
|
|
392
|
+
this.handleDocumentMouseMove(e);
|
|
393
|
+
|
|
291
394
|
connectedCallback() {
|
|
292
395
|
this.setPositionCheckLoop();
|
|
293
396
|
|
|
@@ -300,8 +403,14 @@ const ConfigureWebviewTags = (
|
|
|
300
403
|
// we still need to send it to objc to calculate from its bottom left position
|
|
301
404
|
// otherwise it'll move around unexpectedly.
|
|
302
405
|
window.addEventListener("resize", this.boundForceSyncDimensions);
|
|
303
|
-
|
|
304
406
|
window.addEventListener("scroll", this.boundSyncDimensions);
|
|
407
|
+
// Note: mousemove won't fire during a drag so we get that behaviour
|
|
408
|
+
// for free without doing calculations.
|
|
409
|
+
document.addEventListener(
|
|
410
|
+
"mousemove",
|
|
411
|
+
this.boundhandleDocumentMouseMove,
|
|
412
|
+
true
|
|
413
|
+
);
|
|
305
414
|
|
|
306
415
|
// todo: For chromium webviews (windows native or chromium bundled)
|
|
307
416
|
// should be able to use performanceObservers on layout-shift to
|
|
@@ -318,6 +427,10 @@ const ConfigureWebviewTags = (
|
|
|
318
427
|
// this.mutationObserver?.disconnect();
|
|
319
428
|
window.removeEventListener("resize", this.boundForceSyncDimensions);
|
|
320
429
|
window.removeEventListener("scroll", this.boundSyncDimensions);
|
|
430
|
+
document.removeEventListener(
|
|
431
|
+
"mousemove",
|
|
432
|
+
this.boundhandleDocumentMouseMove
|
|
433
|
+
);
|
|
321
434
|
this.zigRpc.send.webviewTagRemove({ id: this.webviewId });
|
|
322
435
|
}
|
|
323
436
|
|
|
@@ -407,9 +520,8 @@ const ConfigureWebviewTags = (
|
|
|
407
520
|
DEFAULT_FRAME_RATE = Math.round(1000 / 30); // 30fps
|
|
408
521
|
streamScreenInterval?: Timer;
|
|
409
522
|
|
|
410
|
-
//
|
|
411
|
-
|
|
412
|
-
startMirroring(frameRate: number = this.DEFAULT_FRAME_RATE) {
|
|
523
|
+
// NOTE: This is very cpu intensive, Prefer startMirroring where possible
|
|
524
|
+
startMirroringToDom(frameRate: number = this.DEFAULT_FRAME_RATE) {
|
|
413
525
|
if (this.streamScreenInterval) {
|
|
414
526
|
clearInterval(this.streamScreenInterval);
|
|
415
527
|
}
|
|
@@ -419,13 +531,33 @@ const ConfigureWebviewTags = (
|
|
|
419
531
|
}, frameRate);
|
|
420
532
|
}
|
|
421
533
|
|
|
422
|
-
|
|
534
|
+
stopMirroringToDom() {
|
|
423
535
|
if (this.streamScreenInterval) {
|
|
424
536
|
clearInterval(this.streamScreenInterval);
|
|
425
537
|
this.streamScreenInterval = undefined;
|
|
426
538
|
}
|
|
427
539
|
}
|
|
428
540
|
|
|
541
|
+
startMirroring() {
|
|
542
|
+
if (this.isMirroring === false) {
|
|
543
|
+
this.isMirroring = true;
|
|
544
|
+
this.zigRpc.send.webviewTagToggleMirroring({
|
|
545
|
+
id: this.webviewId,
|
|
546
|
+
enable: true,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
stopMirroring() {
|
|
552
|
+
if (this.isMirroring === true) {
|
|
553
|
+
this.isMirroring = false;
|
|
554
|
+
this.zigRpc.send.webviewTagToggleMirroring({
|
|
555
|
+
id: this.webviewId,
|
|
556
|
+
enable: false,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
429
561
|
clearScreenImage() {
|
|
430
562
|
this.style.backgroundImage = "";
|
|
431
563
|
}
|
|
@@ -503,11 +635,11 @@ const ConfigureWebviewTags = (
|
|
|
503
635
|
this.syncScreenshot(() => {
|
|
504
636
|
this.delegateMode = true;
|
|
505
637
|
this.toggleTransparent(true, true);
|
|
506
|
-
this.
|
|
638
|
+
this.startMirroringToDom();
|
|
507
639
|
});
|
|
508
640
|
} else {
|
|
509
641
|
this.delegateMode = false;
|
|
510
|
-
this.
|
|
642
|
+
this.stopMirroringToDom();
|
|
511
643
|
this.toggleTransparent(this.transparent);
|
|
512
644
|
this.tryClearScreenImage();
|
|
513
645
|
}
|
|
@@ -527,10 +659,10 @@ const ConfigureWebviewTags = (
|
|
|
527
659
|
this.hiddenMirrorMode = true;
|
|
528
660
|
this.toggleHidden(true, true);
|
|
529
661
|
this.togglePassthrough(true, true);
|
|
530
|
-
this.
|
|
662
|
+
this.startMirroringToDom();
|
|
531
663
|
});
|
|
532
664
|
} else {
|
|
533
|
-
this.
|
|
665
|
+
this.stopMirroringToDom();
|
|
534
666
|
this.toggleHidden(this.hidden);
|
|
535
667
|
this.togglePassthrough(this.passthroughEnabled);
|
|
536
668
|
this.tryClearScreenImage();
|
|
@@ -16,6 +16,8 @@ import type { BuiltinBunToWebviewSchema } from "../../browser/builtinrpcSchema";
|
|
|
16
16
|
const BrowserViewMap = {};
|
|
17
17
|
let nextWebviewId = 1;
|
|
18
18
|
|
|
19
|
+
const CHUNK_SIZE = 4096; // 4KB
|
|
20
|
+
|
|
19
21
|
type BrowserViewOptions<T = undefined> = {
|
|
20
22
|
url: string | null;
|
|
21
23
|
html: string | null;
|
|
@@ -30,6 +32,7 @@ type BrowserViewOptions<T = undefined> = {
|
|
|
30
32
|
rpc: T;
|
|
31
33
|
syncRpc: { [method: string]: (params: any) => any };
|
|
32
34
|
hostWebviewId: number;
|
|
35
|
+
autoResize: boolean;
|
|
33
36
|
};
|
|
34
37
|
|
|
35
38
|
interface ElectrobunWebviewRPCSChema {
|
|
@@ -66,6 +69,7 @@ const internalSyncRpcHandlers = {
|
|
|
66
69
|
partition,
|
|
67
70
|
frame,
|
|
68
71
|
hostWebviewId,
|
|
72
|
+
autoResize: false,
|
|
69
73
|
});
|
|
70
74
|
|
|
71
75
|
// Note: we have to give it a couple of ticks to fully create the browserview
|
|
@@ -102,6 +106,7 @@ export class BrowserView<T> {
|
|
|
102
106
|
html: string | null = null;
|
|
103
107
|
preload: string | null = null;
|
|
104
108
|
partition: string | null = null;
|
|
109
|
+
autoResize: boolean = true;
|
|
105
110
|
frame: {
|
|
106
111
|
x: number;
|
|
107
112
|
y: number;
|
|
@@ -133,6 +138,7 @@ export class BrowserView<T> {
|
|
|
133
138
|
// file exists first
|
|
134
139
|
this.pipePrefix = `/private/tmp/electrobun_ipc_pipe_${hash}_${randomId}_${this.id}`;
|
|
135
140
|
this.hostWebviewId = options.hostWebviewId;
|
|
141
|
+
this.autoResize = options.autoResize === false ? false : true;
|
|
136
142
|
|
|
137
143
|
this.init();
|
|
138
144
|
}
|
|
@@ -156,7 +162,7 @@ export class BrowserView<T> {
|
|
|
156
162
|
x: this.frame.x,
|
|
157
163
|
y: this.frame.y,
|
|
158
164
|
},
|
|
159
|
-
|
|
165
|
+
autoResize: this.autoResize,
|
|
160
166
|
});
|
|
161
167
|
|
|
162
168
|
this.createStreams();
|
|
@@ -213,8 +219,19 @@ export class BrowserView<T> {
|
|
|
213
219
|
this.executeJavascript(wrappedMessage);
|
|
214
220
|
}
|
|
215
221
|
|
|
222
|
+
// Note: the OS has a buffer limit on named pipes. If we overflow it
|
|
223
|
+
// it won't trigger the kevent for zig to read the pipe and we'll be stuck.
|
|
224
|
+
// so we have to chunk it
|
|
216
225
|
executeJavascript(js: string) {
|
|
217
|
-
|
|
226
|
+
let offset = 0;
|
|
227
|
+
while (offset < js.length) {
|
|
228
|
+
const chunk = js.slice(offset, offset + CHUNK_SIZE);
|
|
229
|
+
this.inStream.write(chunk);
|
|
230
|
+
offset += CHUNK_SIZE;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Ensure the newline is written after all chunks
|
|
234
|
+
this.inStream.write("\n");
|
|
218
235
|
}
|
|
219
236
|
|
|
220
237
|
loadURL(url: string) {
|
package/src/bun/proc/zig.ts
CHANGED
package/src/cli/build/electrobun
CHANGED
|
Binary file
|
package/src/cli/index.ts
CHANGED
|
@@ -716,7 +716,9 @@ if (commandArg === "init") {
|
|
|
716
716
|
const cacheBuster = Math.random().toString(36).substring(7);
|
|
717
717
|
const updateJsonResponse = await fetch(
|
|
718
718
|
urlToPrevUpdateJson + `?${cacheBuster}`
|
|
719
|
-
)
|
|
719
|
+
).catch((err) => {
|
|
720
|
+
console.log("bucketURL not found: ", err);
|
|
721
|
+
});
|
|
720
722
|
|
|
721
723
|
const urlToLatestTarball = join(
|
|
722
724
|
config.release.bucketUrl,
|
|
Binary file
|
|
Binary file
|
package/docs/architecture.md
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
## Project Structure
|
|
2
|
-
|
|
3
|
-
<pre>
|
|
4
|
-
/src - electrobun's src code
|
|
5
|
-
/src/browser - typescript compiles to the in-webview electrobun javascript api
|
|
6
|
-
/src/bun - typescript compiles to the main process javascript api
|
|
7
|
-
/src/objc - c-abi wrapped objective c, compiled to a static lib
|
|
8
|
-
/src/zig - zig native bindings, compiles to the native renderer process.
|
|
9
|
-
/src/zig/build - where the compiled src/objc ends up so zig can see it and embed it
|
|
10
|
-
/src/cli - a cli for building and running developer apps, it reads electrobun.config files
|
|
11
|
-
/example - Interactive example using the library.
|
|
12
|
-
</pre>
|
|
13
|
-
|
|
14
|
-
## Building
|
|
15
|
-
|
|
16
|
-
Tldr;
|
|
17
|
-
|
|
18
|
-
- clang to compile objective c wrappers for macos in src/objc (.m files) into a static library, since objc is a superset of c the wrappers have intentionally been designed with c-compatible wrappers/apis
|
|
19
|
-
- zig is built with zig's build system. must specify zig equivalent types for objc wrappers to map memory
|
|
20
|
-
- electrobun in-webview-api that runs in all frames of the webviews is built using bun with a browser target
|
|
21
|
-
- the in-webview-api and objc are built into src/zig/build/ so zig can see it
|
|
22
|
-
|
|
23
|
-
## IPC
|
|
24
|
-
|
|
25
|
-
Bun spawns the zig bindings as a separate process. GUI applications require the main thread of a process to run a blocking event loop, so this is separate to the bun process.
|
|
26
|
-
|
|
27
|
-
There are two categories of named pipes for IPC.
|
|
28
|
-
|
|
29
|
-
1. bun <-> zig. This is used for communicating with zig. ie: creating windows and webviews, and binding native handlers that need a response from bun like will-navigate.
|
|
30
|
-
2. bun <-> webview. Each webview gets its own pair of named pipes for communicating with bun.
|
|
31
|
-
|
|
32
|
-
### bun <-> zig
|
|
33
|
-
|
|
34
|
-
There is a primary named pipe used to send rpc-anywhere encoded json messages between bun and zig on this pipe. There is a minimal zig implementation of rpc-anywhere that also implements a promise-like api in zig (freezes the main thread while waiting and unfreezes it returning a value).
|
|
35
|
-
|
|
36
|
-
In order to simulate something promise-like in zig we send the request to bun and pause the main zig thread. We have a pipe listener on another thread waiting for a reply, when it gets a response it unfreezes the main thread and returns the value back to the function that was waiting.
|
|
37
|
-
|
|
38
|
-
In general (whether receiving rpc requests, responses, and messages from bun) there is a performant loop in zig listening on another thread. We use native functions to group named pipe into a kqueue and let the process subscribe to events that continue the loop so it isn't busy waiting. For any gui-related rpc whe have to pass messages to the main thread to be executed.
|
|
39
|
-
|
|
40
|
-
### bun <-> webview
|
|
41
|
-
|
|
42
|
-
When creating a webview a new named pipe pair is created. zig creates a bridge between the named pipe and the webview that passes anything between the named pipe and the webview so it's never deserialized in zig. We also have code in the webview for handling the other end of the RPC anywhere bun<->webview communication. By using a named pipe for each webview we eliminate the need for the zig bridge having to double wrap or partially de/serialize json messages to route them to the right webview which makes bun <-> webview communciation significantly faster.
|
|
43
|
-
|
|
44
|
-
## Events
|
|
45
|
-
|
|
46
|
-
Electrobun.events is a custom event emitter. If we look at how the 'will-navigate' event works.
|
|
47
|
-
|
|
48
|
-
1. zig sends json rpc request to decide navigation over the main bun named pipe with a url and a webviewid
|
|
49
|
-
2. zig pauses the main thread simulating a promise-ish while listening on a separate named pipe listener thread
|
|
50
|
-
3. bun parses the json rpc and sees it's a 'will navigate' request
|
|
51
|
-
4. bun creates a new Electrobun.events.webview.willNavigate event with the data (url and webviewid).
|
|
52
|
-
5. bun then emits the event globally ('will-navigate') that could be listened to with Electrobun.events.on('will-navigate')
|
|
53
|
-
6. bun the passes the same event to a specific event ('will-navigate-<webviewId>' ie: will-navigate-1 for the webivew with id 1)
|
|
54
|
-
7. you could listen to this globally via Electrobun.events.on('will-navigate-1') if you wanted to. Since ids are incremented deterministically this allows for some interesting patterns like handling navigation for the first window created
|
|
55
|
-
8. you can also listen on the webview with myWebview.on('will-navigate'). Webview extends a "event emitter class' that essentially provides on, off, appendEventListener, etc. but instead of being an event listener it modifies the event name to include its id, and subscribes the handler to the main event emitter. So it basically does Electrobun.events.on('will-navigate-1') for you without you having to think about the webview's id and providing other lifecycle handling for you.
|
|
56
|
-
9. the event object has a response getter/setter and a clearResponse method that allows any handler to respond with a value as well as a 'responseWasSet' flag. so in any handler you can both check if another handler set a response, and if you want override or modify the response.
|
|
57
|
-
10. the response is then sent serialized as an rpc response and sent back to zig which is listening on another thread and unfreezes the main thread returning the value
|
|
58
|
-
|
|
59
|
-
Global events are always called before specific events.
|
|
60
|
-
|
|
61
|
-
## Working on Electrobun
|
|
62
|
-
|
|
63
|
-
There are some npm scripts to facilitate building everything from the objc, zig, bundling webview api, transpiling the bun api and so an, as well as building the example app and executing it.
|
|
64
|
-
|
|
65
|
-
The example app is meant to be an interactive example of Electrobun's functionality, it's useful when implementing new functionality in any part of Electrobun to have everything rebuilt so you can interact with it in the example app which then doubles as a demo app for developers wanting to explore what Electrobun can do.
|
|
66
|
-
|
|
67
|
-
You currently need zig installed globally, and to be on an ARM mac. I dunno if you have to install xcode or xcode tools to get clang on your system. Will iron out a better dev flow in the future.
|
|
68
|
-
|
|
69
|
-
For now you can simply
|
|
70
|
-
|
|
71
|
-
1. clone the repo
|
|
72
|
-
2. in the repo root run `bun run dev:example`
|
|
73
|
-
|
|
74
|
-
If you take a look at the repo's package.json as well as example app's package.json and electrobun.config you'll get a better sense of what's happening for each step.
|
|
75
|
-
|
|
76
|
-
## How Developer apps are built
|
|
77
|
-
|
|
78
|
-
> Note: in order for an application to get keyboard focus on macos you can't run it as a subprocess of the terminal which greedily steals keyboard input, so it needs to be built into an app bundle.
|
|
79
|
-
|
|
80
|
-
This part is wip, but currently we create a minimal macos app bundle and execute it. There is a launcher shell script which calls bun with your typescript. It configures stdout/err to write to a named pipe log file and starts listening to it so you get the output in the terminal.
|
|
81
|
-
|
|
82
|
-
You can cmd+c to stop that, but for now to quit your running app you have to close the window.
|
|
83
|
-
|
|
84
|
-
A better dev flow, as well as installing bun to a global location outside the app bundle is being actively developed.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|