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.
- package/BUILD.md +90 -0
- package/README.md +1 -1
- package/bin/electrobun.cjs +2 -9
- package/debug.js +5 -0
- package/dist/api/browser/builtinrpcSchema.ts +19 -0
- package/dist/api/browser/index.ts +409 -0
- package/dist/api/browser/rpc/webview.ts +79 -0
- package/dist/api/browser/stylesAndElements.ts +3 -0
- package/dist/api/browser/webviewtag.ts +586 -0
- package/dist/api/bun/ElectrobunConfig.ts +171 -0
- package/dist/api/bun/core/ApplicationMenu.ts +66 -0
- package/dist/api/bun/core/BrowserView.ts +349 -0
- package/dist/api/bun/core/BrowserWindow.ts +195 -0
- package/dist/api/bun/core/ContextMenu.ts +67 -0
- package/dist/api/bun/core/Paths.ts +5 -0
- package/dist/api/bun/core/Socket.ts +181 -0
- package/dist/api/bun/core/Tray.ts +121 -0
- package/dist/api/bun/core/Updater.ts +681 -0
- package/dist/api/bun/core/Utils.ts +48 -0
- package/dist/api/bun/events/ApplicationEvents.ts +14 -0
- package/dist/api/bun/events/event.ts +29 -0
- package/dist/api/bun/events/eventEmitter.ts +45 -0
- package/dist/api/bun/events/trayEvents.ts +9 -0
- package/dist/api/bun/events/webviewEvents.ts +16 -0
- package/dist/api/bun/events/windowEvents.ts +12 -0
- package/dist/api/bun/index.ts +47 -0
- package/dist/api/bun/proc/linux.md +43 -0
- package/dist/api/bun/proc/native.ts +1322 -0
- package/dist/api/shared/platform.ts +48 -0
- package/dist/main.js +54 -0
- package/package.json +11 -6
- package/src/cli/index.ts +1353 -239
- package/templates/hello-world/README.md +57 -0
- package/templates/hello-world/bun.lock +225 -0
- package/templates/hello-world/electrobun.config.ts +28 -0
- package/templates/hello-world/package.json +16 -0
- package/templates/hello-world/src/bun/index.ts +15 -0
- package/templates/hello-world/src/mainview/index.css +124 -0
- package/templates/hello-world/src/mainview/index.html +46 -0
- package/templates/hello-world/src/mainview/index.ts +1 -0
- package/templates/interactive-playground/README.md +26 -0
- package/templates/interactive-playground/assets/tray-icon.png +0 -0
- package/templates/interactive-playground/electrobun.config.ts +36 -0
- package/templates/interactive-playground/package-lock.json +36 -0
- package/templates/interactive-playground/package.json +15 -0
- package/templates/interactive-playground/src/bun/demos/files.ts +70 -0
- package/templates/interactive-playground/src/bun/demos/menus.ts +139 -0
- package/templates/interactive-playground/src/bun/demos/rpc.ts +83 -0
- package/templates/interactive-playground/src/bun/demos/system.ts +72 -0
- package/templates/interactive-playground/src/bun/demos/updates.ts +105 -0
- package/templates/interactive-playground/src/bun/demos/windows.ts +90 -0
- package/templates/interactive-playground/src/bun/index.ts +124 -0
- package/templates/interactive-playground/src/bun/types/rpc.ts +109 -0
- package/templates/interactive-playground/src/mainview/components/EventLog.ts +107 -0
- package/templates/interactive-playground/src/mainview/components/Sidebar.ts +65 -0
- package/templates/interactive-playground/src/mainview/components/Toast.ts +57 -0
- package/templates/interactive-playground/src/mainview/demos/FileDemo.ts +211 -0
- package/templates/interactive-playground/src/mainview/demos/MenuDemo.ts +102 -0
- package/templates/interactive-playground/src/mainview/demos/RPCDemo.ts +229 -0
- package/templates/interactive-playground/src/mainview/demos/TrayDemo.ts +132 -0
- package/templates/interactive-playground/src/mainview/demos/WebViewDemo.ts +411 -0
- package/templates/interactive-playground/src/mainview/demos/WindowDemo.ts +207 -0
- package/templates/interactive-playground/src/mainview/index.css +538 -0
- package/templates/interactive-playground/src/mainview/index.html +103 -0
- package/templates/interactive-playground/src/mainview/index.ts +238 -0
- package/templates/multitab-browser/README.md +34 -0
- package/templates/multitab-browser/bun.lock +224 -0
- package/templates/multitab-browser/electrobun.config.ts +32 -0
- package/templates/multitab-browser/package-lock.json +20 -0
- package/templates/multitab-browser/package.json +12 -0
- package/templates/multitab-browser/src/bun/index.ts +144 -0
- package/templates/multitab-browser/src/bun/tabManager.ts +200 -0
- package/templates/multitab-browser/src/bun/types/rpc.ts +78 -0
- package/templates/multitab-browser/src/mainview/index.css +487 -0
- package/templates/multitab-browser/src/mainview/index.html +94 -0
- package/templates/multitab-browser/src/mainview/index.ts +630 -0
- package/templates/photo-booth/README.md +108 -0
- package/templates/photo-booth/bun.lock +239 -0
- package/templates/photo-booth/electrobun.config.ts +28 -0
- package/templates/photo-booth/package.json +16 -0
- package/templates/photo-booth/src/bun/index.ts +92 -0
- package/templates/photo-booth/src/mainview/index.css +465 -0
- package/templates/photo-booth/src/mainview/index.html +124 -0
- package/templates/photo-booth/src/mainview/index.ts +499 -0
- package/tests/bun.lock +14 -0
- package/tests/electrobun.config.ts +45 -0
- package/tests/package-lock.json +36 -0
- package/tests/package.json +13 -0
- package/tests/src/bun/index.ts +100 -0
- package/tests/src/bun/test-runner.ts +508 -0
- package/tests/src/mainview/index.html +110 -0
- package/tests/src/mainview/index.ts +458 -0
- package/tests/src/mainview/styles/main.css +451 -0
- package/tests/src/testviews/tray-test.html +57 -0
- package/tests/src/testviews/webview-mask.html +114 -0
- package/tests/src/testviews/webview-navigation.html +36 -0
- package/tests/src/testviews/window-create.html +17 -0
- package/tests/src/testviews/window-events.html +29 -0
- package/tests/src/testviews/window-focus.html +37 -0
- package/tests/src/webviewtag/index.ts +11 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Electrobun configuration type definitions
|
|
3
|
+
* Used in electrobun.config.ts files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface ElectrobunConfig {
|
|
7
|
+
/**
|
|
8
|
+
* Application metadata configuration
|
|
9
|
+
*/
|
|
10
|
+
app: {
|
|
11
|
+
/**
|
|
12
|
+
* The display name of your application
|
|
13
|
+
*/
|
|
14
|
+
name: string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Unique identifier for your application (e.g., "com.example.myapp")
|
|
18
|
+
* Used for platform-specific identifiers
|
|
19
|
+
*/
|
|
20
|
+
identifier: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Application version string (e.g., "1.0.0")
|
|
24
|
+
*/
|
|
25
|
+
version: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Build configuration options
|
|
30
|
+
*/
|
|
31
|
+
build?: {
|
|
32
|
+
/**
|
|
33
|
+
* Bun process build configuration
|
|
34
|
+
*/
|
|
35
|
+
bun?: {
|
|
36
|
+
/**
|
|
37
|
+
* Entry point for the main Bun process
|
|
38
|
+
* @default "src/bun/index.ts"
|
|
39
|
+
*/
|
|
40
|
+
entrypoint?: string;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* External modules to exclude from bundling
|
|
44
|
+
* @default []
|
|
45
|
+
*/
|
|
46
|
+
external?: string[];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Browser view build configurations
|
|
51
|
+
*/
|
|
52
|
+
views?: {
|
|
53
|
+
[viewName: string]: {
|
|
54
|
+
/**
|
|
55
|
+
* Entry point for this view's TypeScript code
|
|
56
|
+
*/
|
|
57
|
+
entrypoint: string;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* External modules to exclude from bundling for this view
|
|
61
|
+
*/
|
|
62
|
+
external?: string[];
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Files to copy directly to the build output
|
|
68
|
+
* Key is source path, value is destination path
|
|
69
|
+
*/
|
|
70
|
+
copy?: {
|
|
71
|
+
[sourcePath: string]: string;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Output folder for built application
|
|
75
|
+
* @default "build"
|
|
76
|
+
*/
|
|
77
|
+
buildFolder?: string;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Output folder for distribution artifacts
|
|
81
|
+
* @default "artifacts"
|
|
82
|
+
*/
|
|
83
|
+
artifactFolder?: string;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Build targets to compile for
|
|
87
|
+
* Can be "current", "all", or comma-separated list like "macos-arm64,win-x64"
|
|
88
|
+
*/
|
|
89
|
+
targets?: string;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* macOS-specific build configuration
|
|
93
|
+
*/
|
|
94
|
+
mac?: {
|
|
95
|
+
/**
|
|
96
|
+
* Enable code signing for macOS builds
|
|
97
|
+
* @default false
|
|
98
|
+
*/
|
|
99
|
+
codesign?: boolean;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Enable notarization for macOS builds (requires codesign)
|
|
103
|
+
* @default false
|
|
104
|
+
*/
|
|
105
|
+
notarize?: boolean;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Bundle CEF (Chromium Embedded Framework) instead of using system WebView
|
|
109
|
+
* @default false
|
|
110
|
+
*/
|
|
111
|
+
bundleCEF?: boolean;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* macOS entitlements for code signing
|
|
115
|
+
*/
|
|
116
|
+
entitlements?: Record<string, boolean | string>;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Path to .iconset folder containing app icons
|
|
120
|
+
* @default "icon.iconset"
|
|
121
|
+
*/
|
|
122
|
+
icons?: string;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Windows-specific build configuration
|
|
127
|
+
*/
|
|
128
|
+
win?: {
|
|
129
|
+
/**
|
|
130
|
+
* Bundle CEF (Chromium Embedded Framework) instead of using WebView2
|
|
131
|
+
* @default false
|
|
132
|
+
*/
|
|
133
|
+
bundleCEF?: boolean;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Linux-specific build configuration
|
|
138
|
+
*/
|
|
139
|
+
linux?: {
|
|
140
|
+
/**
|
|
141
|
+
* Bundle CEF (Chromium Embedded Framework) instead of using GTKWebKit
|
|
142
|
+
* Recommended on Linux for advanced layer compositing features
|
|
143
|
+
* @default false
|
|
144
|
+
*/
|
|
145
|
+
bundleCEF?: boolean;
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Build scripts configuration
|
|
151
|
+
*/
|
|
152
|
+
scripts?: {
|
|
153
|
+
/**
|
|
154
|
+
* Script to run after build completes
|
|
155
|
+
* Can be a path to a script file
|
|
156
|
+
*/
|
|
157
|
+
postBuild?: string;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Release and distribution configuration
|
|
162
|
+
*/
|
|
163
|
+
release?: {
|
|
164
|
+
/**
|
|
165
|
+
* Base URL for artifact distribution (e.g., S3 bucket URL)
|
|
166
|
+
* Used for auto-updates and patch generation
|
|
167
|
+
*/
|
|
168
|
+
bucketUrl?: string;
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ffi, type ApplicationMenuItemConfig } from "../proc/native";
|
|
2
|
+
import electrobunEventEmitter from "../events/eventEmitter";
|
|
3
|
+
|
|
4
|
+
export const setApplicationMenu = (menu: Array<ApplicationMenuItemConfig>) => {
|
|
5
|
+
const menuWithDefaults = menuConfigWithDefaults(menu);
|
|
6
|
+
ffi.request.setApplicationMenu({
|
|
7
|
+
menuConfig: JSON.stringify(menuWithDefaults),
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const on = (name: "application-menu-clicked", handler) => {
|
|
12
|
+
const specificName = `${name}`;
|
|
13
|
+
electrobunEventEmitter.on(specificName, handler);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const roleLabelMap = {
|
|
17
|
+
quit: "Quit",
|
|
18
|
+
hide: "Hide",
|
|
19
|
+
hideOthers: "Hide Others",
|
|
20
|
+
showAll: "Show All",
|
|
21
|
+
undo: "Undo",
|
|
22
|
+
redo: "Redo",
|
|
23
|
+
cut: "Cut",
|
|
24
|
+
copy: "Copy",
|
|
25
|
+
paste: "Paste",
|
|
26
|
+
pasteAndMatchStyle: "Paste And Match Style",
|
|
27
|
+
delete: "Delete",
|
|
28
|
+
selectAll: "Select All",
|
|
29
|
+
startSpeaking: "Start Speaking",
|
|
30
|
+
stopSpeaking: "Stop Speaking",
|
|
31
|
+
enterFullScreen: "Enter FullScreen",
|
|
32
|
+
exitFullScreen: "Exit FullScreen",
|
|
33
|
+
toggleFullScreen: "Toggle Full Screen",
|
|
34
|
+
minimize: "Minimize",
|
|
35
|
+
zoom: "Zoom",
|
|
36
|
+
bringAllToFront: "Bring All To Front",
|
|
37
|
+
close: "Close",
|
|
38
|
+
cycleThroughWindows: "Cycle Through Windows",
|
|
39
|
+
showHelp: "Show Help",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const menuConfigWithDefaults = (
|
|
43
|
+
menu: Array<ApplicationMenuItemConfig>
|
|
44
|
+
): Array<ApplicationMenuItemConfig> => {
|
|
45
|
+
return menu.map((item) => {
|
|
46
|
+
if (item.type === "divider" || item.type === "separator") {
|
|
47
|
+
return { type: "divider" };
|
|
48
|
+
} else {
|
|
49
|
+
return {
|
|
50
|
+
label: item.label || roleLabelMap[item.role] || "",
|
|
51
|
+
type: item.type || "normal",
|
|
52
|
+
// application menus can either have an action or a role. not both.
|
|
53
|
+
...(item.role ? { role: item.role } : { action: item.action || "" }),
|
|
54
|
+
// default enabled to true unless explicitly set to false
|
|
55
|
+
enabled: item.enabled === false ? false : true,
|
|
56
|
+
checked: Boolean(item.checked),
|
|
57
|
+
hidden: Boolean(item.hidden),
|
|
58
|
+
tooltip: item.tooltip || undefined,
|
|
59
|
+
accelerator: item.accelerator || undefined,
|
|
60
|
+
...(item.submenu
|
|
61
|
+
? { submenu: menuConfigWithDefaults(item.submenu) }
|
|
62
|
+
: {}),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
};
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { native, toCString, ffi } from "../proc/native";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
import electrobunEventEmitter from "../events/eventEmitter";
|
|
5
|
+
import {
|
|
6
|
+
type RPCSchema,
|
|
7
|
+
type RPCRequestHandler,
|
|
8
|
+
type RPCMessageHandlerFn,
|
|
9
|
+
type WildcardRPCMessageHandlerFn,
|
|
10
|
+
type RPCOptions,
|
|
11
|
+
createRPC,
|
|
12
|
+
} from "rpc-anywhere";
|
|
13
|
+
import { Updater } from "./Updater";
|
|
14
|
+
import type { BuiltinBunToWebviewSchema,BuiltinWebviewToBunSchema } from "../../browser/builtinrpcSchema";
|
|
15
|
+
import { rpcPort, sendMessageToWebviewViaSocket } from "./Socket";
|
|
16
|
+
import { randomBytes } from "crypto";
|
|
17
|
+
import {FFIType, type Pointer} from 'bun:ffi';
|
|
18
|
+
|
|
19
|
+
const BrowserViewMap: {
|
|
20
|
+
[id: number]: BrowserView<any>;
|
|
21
|
+
} = {};
|
|
22
|
+
let nextWebviewId = 1;
|
|
23
|
+
|
|
24
|
+
const CHUNK_SIZE = 1024 * 4; // 4KB
|
|
25
|
+
|
|
26
|
+
type BrowserViewOptions<T = undefined> = {
|
|
27
|
+
url: string | null;
|
|
28
|
+
html: string | null;
|
|
29
|
+
preload: string | null;
|
|
30
|
+
renderer: 'native' | 'cef';
|
|
31
|
+
partition: string | null;
|
|
32
|
+
frame: {
|
|
33
|
+
x: number;
|
|
34
|
+
y: number;
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
};
|
|
38
|
+
rpc: T;
|
|
39
|
+
hostWebviewId: number;
|
|
40
|
+
autoResize: boolean;
|
|
41
|
+
|
|
42
|
+
windowId: number;
|
|
43
|
+
navigationRules: string | null;
|
|
44
|
+
// renderer:
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
interface ElectrobunWebviewRPCSChema {
|
|
48
|
+
bun: RPCSchema;
|
|
49
|
+
webview: RPCSchema;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const defaultOptions: Partial<BrowserViewOptions> = {
|
|
53
|
+
url: null,
|
|
54
|
+
html: null,
|
|
55
|
+
preload: null,
|
|
56
|
+
renderer: 'native',
|
|
57
|
+
frame: {
|
|
58
|
+
x: 0,
|
|
59
|
+
y: 0,
|
|
60
|
+
width: 800,
|
|
61
|
+
height: 600,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
const hash = await Updater.localInfo.hash();
|
|
68
|
+
// Note: we use the build's hash to separate from different apps and different builds
|
|
69
|
+
// but we also want a randomId to separate different instances of the same app
|
|
70
|
+
const randomId = Math.random().toString(36).substring(7);
|
|
71
|
+
|
|
72
|
+
export class BrowserView<T> {
|
|
73
|
+
id: number = nextWebviewId++;
|
|
74
|
+
ptr: Pointer;
|
|
75
|
+
hostWebviewId?: number;
|
|
76
|
+
windowId: number;
|
|
77
|
+
renderer: 'cef' | 'native';
|
|
78
|
+
url: string | null = null;
|
|
79
|
+
html: string | null = null;
|
|
80
|
+
preload: string | null = null;
|
|
81
|
+
partition: string | null = null;
|
|
82
|
+
autoResize: boolean = true;
|
|
83
|
+
frame: {
|
|
84
|
+
x: number;
|
|
85
|
+
y: number;
|
|
86
|
+
width: number;
|
|
87
|
+
height: number;
|
|
88
|
+
} = {
|
|
89
|
+
x: 0,
|
|
90
|
+
y: 0,
|
|
91
|
+
width: 800,
|
|
92
|
+
height: 600,
|
|
93
|
+
};
|
|
94
|
+
pipePrefix: string;
|
|
95
|
+
inStream: fs.WriteStream;
|
|
96
|
+
outStream: ReadableStream<Uint8Array>;
|
|
97
|
+
secretKey: Uint8Array;
|
|
98
|
+
rpc?: T;
|
|
99
|
+
rpcHandler?: (msg: any) => void;
|
|
100
|
+
navigationRules: string | null;
|
|
101
|
+
|
|
102
|
+
constructor(options: Partial<BrowserViewOptions<T>> = defaultOptions) {
|
|
103
|
+
// const rpc = options.rpc;
|
|
104
|
+
|
|
105
|
+
this.url = options.url || defaultOptions.url || null;
|
|
106
|
+
this.html = options.html || defaultOptions.html || null;
|
|
107
|
+
this.preload = options.preload || defaultOptions.preload || null;
|
|
108
|
+
this.frame = options.frame
|
|
109
|
+
? { ...defaultOptions.frame, ...options.frame }
|
|
110
|
+
: { ...defaultOptions.frame };
|
|
111
|
+
this.rpc = options.rpc;
|
|
112
|
+
this.secretKey = new Uint8Array(randomBytes(32));
|
|
113
|
+
this.partition = options.partition || null;
|
|
114
|
+
// todo (yoav): since collisions can crash the app add a function that checks if the
|
|
115
|
+
// file exists first
|
|
116
|
+
this.pipePrefix = `/private/tmp/electrobun_ipc_pipe_${hash}_${randomId}_${this.id}`;
|
|
117
|
+
this.hostWebviewId = options.hostWebviewId;
|
|
118
|
+
this.windowId = options.windowId;
|
|
119
|
+
this.autoResize = options.autoResize === false ? false : true;
|
|
120
|
+
this.navigationRules = options.navigationRules || null;
|
|
121
|
+
this.renderer = options.renderer || defaultOptions.renderer;
|
|
122
|
+
|
|
123
|
+
BrowserViewMap[this.id] = this;
|
|
124
|
+
this.ptr = this.init();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
init() {
|
|
128
|
+
console.log('browserView init', this.id, this.windowId, this.renderer);
|
|
129
|
+
this.createStreams();
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
console.log('ffi createWEbview')
|
|
133
|
+
// TODO: add a then to this that fires an onReady event
|
|
134
|
+
return ffi.request.createWebview({
|
|
135
|
+
id: this.id,
|
|
136
|
+
windowId: this.windowId,
|
|
137
|
+
renderer: this.renderer,
|
|
138
|
+
rpcPort: rpcPort,
|
|
139
|
+
// todo: consider sending secretKey as base64
|
|
140
|
+
secretKey: this.secretKey.toString(),
|
|
141
|
+
hostWebviewId: this.hostWebviewId || null,
|
|
142
|
+
pipePrefix: this.pipePrefix,
|
|
143
|
+
partition: this.partition,
|
|
144
|
+
url: this.url,
|
|
145
|
+
html: this.html,
|
|
146
|
+
preload: this.preload,
|
|
147
|
+
frame: {
|
|
148
|
+
width: this.frame.width,
|
|
149
|
+
height: this.frame.height,
|
|
150
|
+
x: this.frame.x,
|
|
151
|
+
y: this.frame.y,
|
|
152
|
+
},
|
|
153
|
+
autoResize: this.autoResize,
|
|
154
|
+
navigationRules: this.navigationRules,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
createStreams() {
|
|
161
|
+
if (!this.rpc) {
|
|
162
|
+
this.rpc = BrowserView.defineRPC({
|
|
163
|
+
handlers: { requests: {}, messages: {} },
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this.rpc.setTransport(this.createTransport());
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
sendMessageToWebviewViaExecute(jsonMessage) {
|
|
172
|
+
const stringifiedMessage =
|
|
173
|
+
typeof jsonMessage === "string"
|
|
174
|
+
? jsonMessage
|
|
175
|
+
: JSON.stringify(jsonMessage);
|
|
176
|
+
// todo (yoav): make this a shared const with the browser api
|
|
177
|
+
const wrappedMessage = `window.__electrobun.receiveMessageFromBun(${stringifiedMessage})`;
|
|
178
|
+
this.executeJavascript(wrappedMessage);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
sendInternalMessageViaExecute(jsonMessage) {
|
|
182
|
+
const stringifiedMessage =
|
|
183
|
+
typeof jsonMessage === "string"
|
|
184
|
+
? jsonMessage
|
|
185
|
+
: JSON.stringify(jsonMessage);
|
|
186
|
+
// todo (yoav): make this a shared const with the browser api
|
|
187
|
+
const wrappedMessage = `window.__electrobun.receiveInternalMessageFromBun(${stringifiedMessage})`;
|
|
188
|
+
this.executeJavascript(wrappedMessage);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Note: the OS has a buffer limit on named pipes. If we overflow it
|
|
192
|
+
// it won't trigger the kevent for zig to read the pipe and we'll be stuck.
|
|
193
|
+
// so we have to chunk it
|
|
194
|
+
// TODO: is this still needed after switching from named pipes
|
|
195
|
+
executeJavascript(js: string) {
|
|
196
|
+
ffi.request.evaluateJavascriptWithNoCompletion({id: this.id, js});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
loadURL(url: string) {
|
|
200
|
+
this.url = url;
|
|
201
|
+
native.symbols.loadURLInWebView(this.ptr, toCString(this.url))
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
loadHTML(html: string) {
|
|
205
|
+
// Note: CEF doesn't natively support "setting html" so we just return
|
|
206
|
+
// this BrowserView's html when a special views:// url is hit.
|
|
207
|
+
// So we can update that content and ask the webview to load that url
|
|
208
|
+
this.html = html;
|
|
209
|
+
this.loadURL('views://internal/index.html');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
// todo (yoav): move this to a class that also has off, append, prepend, etc.
|
|
214
|
+
// name should only allow browserView events
|
|
215
|
+
// Note: normalize event names to willNavigate instead of ['will-navigate'] to save
|
|
216
|
+
// 5 characters per usage and allow minification to be more effective.
|
|
217
|
+
on(
|
|
218
|
+
name:
|
|
219
|
+
| "will-navigate"
|
|
220
|
+
| "did-navigate"
|
|
221
|
+
| "did-navigate-in-page"
|
|
222
|
+
| "did-commit-navigation"
|
|
223
|
+
| "dom-ready",
|
|
224
|
+
handler
|
|
225
|
+
) {
|
|
226
|
+
const specificName = `${name}-${this.id}`;
|
|
227
|
+
electrobunEventEmitter.on(specificName, handler);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
createTransport = () => {
|
|
231
|
+
const that = this;
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
send(message: any) {
|
|
235
|
+
const sentOverSocket = sendMessageToWebviewViaSocket(that.id, message);
|
|
236
|
+
|
|
237
|
+
if (!sentOverSocket) {
|
|
238
|
+
try {
|
|
239
|
+
const messageString = JSON.stringify(message);
|
|
240
|
+
that.sendMessageToWebviewViaExecute(messageString);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
console.error("bun: failed to serialize message to webview", error);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
registerHandler(handler) {
|
|
247
|
+
that.rpcHandler = handler;
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
static getById(id: number) {
|
|
253
|
+
return BrowserViewMap[id];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
static getAll() {
|
|
257
|
+
return Object.values(BrowserViewMap);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
static defineRPC<
|
|
261
|
+
Schema extends ElectrobunWebviewRPCSChema,
|
|
262
|
+
BunSchema extends RPCSchema = Schema["bun"],
|
|
263
|
+
WebviewSchema extends RPCSchema = Schema["webview"]
|
|
264
|
+
>(config: {
|
|
265
|
+
maxRequestTime?: number;
|
|
266
|
+
handlers: {
|
|
267
|
+
requests?: RPCRequestHandler<BunSchema["requests"]>;
|
|
268
|
+
messages?: {
|
|
269
|
+
[key in keyof BunSchema["messages"]]: RPCMessageHandlerFn<
|
|
270
|
+
BunSchema["messages"],
|
|
271
|
+
key
|
|
272
|
+
>;
|
|
273
|
+
} & {
|
|
274
|
+
"*"?: WildcardRPCMessageHandlerFn<BunSchema["messages"]>;
|
|
275
|
+
};
|
|
276
|
+
};
|
|
277
|
+
}) {
|
|
278
|
+
// Note: RPC Anywhere requires defining the requests that a schema handles and the messages that a schema sends.
|
|
279
|
+
// eg: BunSchema {
|
|
280
|
+
// requests: // ... requests bun handles, sent by webview
|
|
281
|
+
// messages: // ... messages bun sends, handled by webview
|
|
282
|
+
// }
|
|
283
|
+
// In some generlized contexts that makes sense,
|
|
284
|
+
// In the Electrobun context it can feel a bit counter-intuitive so we swap this around a bit. In Electrobun, the
|
|
285
|
+
// webview and bun are known endpoints so we simplify schema definitions by combining them.
|
|
286
|
+
// Schema {
|
|
287
|
+
// bun: BunSchema {
|
|
288
|
+
// requests: // ... requests bun handles, sent by webview,
|
|
289
|
+
// messages: // ... messages bun handles, sent by webview
|
|
290
|
+
// },
|
|
291
|
+
// webview: WebviewSchema {
|
|
292
|
+
// requests: // ... requests webview handles, sent by bun,
|
|
293
|
+
// messages: // ... messages webview handles, sent by bun
|
|
294
|
+
// },
|
|
295
|
+
// }
|
|
296
|
+
// This way from bun, webview.rpc.request.getTitle() and webview.rpc.send.someMessage maps to the schema
|
|
297
|
+
// MySchema.webview.requests.getTitle and MySchema.webview.messages.someMessage
|
|
298
|
+
// and in the webview, Electroview.rpc.request.getFileContents maps to
|
|
299
|
+
// MySchema.bun.requests.getFileContents.
|
|
300
|
+
// electrobun also treats messages as "requests that we don't wait for to complete", and normalizes specifying the
|
|
301
|
+
// handlers for them alongside request handlers.
|
|
302
|
+
|
|
303
|
+
type mixedWebviewSchema = {
|
|
304
|
+
requests: BunSchema["requests"]// & BuiltinWebviewToBunSchema["requests"];
|
|
305
|
+
messages: WebviewSchema["messages"];
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
type mixedBunSchema = {
|
|
309
|
+
messages: BunSchema["messages"];
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const rpcOptions = {
|
|
313
|
+
maxRequestTime: config.maxRequestTime,
|
|
314
|
+
requestHandler: {
|
|
315
|
+
...config.handlers.requests,
|
|
316
|
+
// ...internalRpcHandlers,
|
|
317
|
+
},
|
|
318
|
+
transport: {
|
|
319
|
+
// Note: RPC Anywhere will throw if you try add a message listener if transport.registerHandler is falsey
|
|
320
|
+
registerHandler: () => {},
|
|
321
|
+
},
|
|
322
|
+
} as RPCOptions<mixedWebviewSchema, mixedBunSchema>;
|
|
323
|
+
|
|
324
|
+
const rpc = createRPC<mixedWebviewSchema, mixedBunSchema>(rpcOptions);
|
|
325
|
+
|
|
326
|
+
const messageHandlers = config.handlers.messages;
|
|
327
|
+
if (messageHandlers) {
|
|
328
|
+
// note: this can only be done once there is a transport
|
|
329
|
+
// @ts-ignore - this is due to all the schema mixing we're doing, fine to ignore
|
|
330
|
+
// while types in here are borked, they resolve correctly/bubble up to the defineRPC call site.
|
|
331
|
+
rpc.addMessageListener(
|
|
332
|
+
"*",
|
|
333
|
+
(messageName: keyof BunSchema["messages"], payload) => {
|
|
334
|
+
const globalHandler = messageHandlers["*"];
|
|
335
|
+
if (globalHandler) {
|
|
336
|
+
globalHandler(messageName, payload);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const messageHandler = messageHandlers[messageName];
|
|
340
|
+
if (messageHandler) {
|
|
341
|
+
messageHandler(payload);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return rpc;
|
|
348
|
+
}
|
|
349
|
+
}
|