electrobun 1.18.1 → 1.18.4-beta.10
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/README.md +37 -14
- package/dash.config.ts +28 -0
- package/dist/api/browser/global.d.ts +6 -0
- package/dist/api/browser/index.ts +162 -44
- package/dist/api/{bun → config}/ElectrobunConfig.ts +109 -40
- package/dist/api/config/validate.test.ts +31 -0
- package/dist/api/config/validate.ts +19 -0
- package/dist/api/preload/.generated/compiled.ts +8 -0
- package/dist/api/{bun/preload → preload}/build.ts +6 -6
- package/dist/api/{bun/preload → preload}/encryption.ts +11 -5
- package/dist/api/{bun/preload → preload}/globals.d.ts +8 -0
- package/dist/api/{bun/preload → preload}/index.ts +18 -8
- package/dist/api/{bun/preload → preload}/webviewTag.ts +32 -3
- package/dist/api/{bun/preload → preload}/wgpuTag.ts +33 -2
- package/dist/api/{bun → sdks/bun}/__tests__/ffi-contract.test.ts +6 -9
- package/dist/api/{bun → sdks/bun}/core/BrowserView.ts +96 -55
- package/dist/api/{bun → sdks/bun}/core/BrowserWindow.ts +20 -30
- package/dist/api/{bun → sdks/bun}/core/BuildConfig.ts +32 -5
- package/dist/api/{bun → sdks/bun}/core/GpuWindow.ts +20 -7
- package/dist/api/sdks/bun/core/Socket.ts +22 -0
- package/dist/api/{bun → sdks/bun}/core/Tray.ts +33 -32
- package/dist/api/{bun → sdks/bun}/core/Updater.ts +10 -10
- package/dist/api/{bun → sdks/bun}/core/Utils.ts +3 -5
- package/dist/api/{bun → sdks/bun}/core/WGPUView.ts +43 -13
- package/dist/api/{bun → sdks/bun}/index.ts +39 -10
- package/dist/api/{bun → sdks/bun}/proc/native.ts +942 -746
- package/dist/api/{bun → sdks/bun}/webGPU.ts +1 -1
- package/dist/api/{bun → sdks/bun}/webgpuAdapter.ts +40 -33
- package/dist/api/shared/build-dependencies.test.ts +48 -0
- package/dist/api/shared/build-dependencies.ts +46 -0
- package/dist/api/shared/go-version.ts +3 -0
- package/dist/api/shared/odin-version.ts +6 -0
- package/dist/api/shared/rust-version.ts +3 -0
- package/dist/go-sdk/callbacks.go +260 -0
- package/dist/go-sdk/electrobun.go +2021 -0
- package/dist/main.js +35 -28
- package/dist/odin-sdk/electrobun/electrobun.odin +2337 -0
- package/dist/preload-full.js +948 -0
- package/dist/preload-sandboxed.js +111 -0
- package/dist/rust-sdk/electrobun.rs +2396 -0
- package/dist/zig-sdk/electrobun.zig +2005 -0
- package/package.json +5 -29
- package/src/cli/index.ts +868 -654
- package/bin/electrobun.cjs +0 -169
- package/dist/api/bun/core/Socket.ts +0 -205
- package/dist/api/bun/core/windowIds.ts +0 -5
- package/dist/api/bun/preload/.generated/compiled.ts +0 -8
- package/dist/api/shared/bun-version.ts +0 -3
- /package/dist/api/{bun/preload → preload}/dragRegions.ts +0 -0
- /package/dist/api/{bun/preload → preload}/events.ts +0 -0
- /package/dist/api/{bun/preload → preload}/index-sandboxed.ts +0 -0
- /package/dist/api/{bun/preload → preload}/internalRpc.ts +0 -0
- /package/dist/api/{bun/preload → preload}/overlaySync.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/core/ApplicationMenu.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/core/ContextMenu.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/core/Paths.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/core/menuRoles.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/events/ApplicationEvents.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/events/event.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/events/eventEmitter.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/events/trayEvents.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/events/webviewEvents.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/events/windowEvents.ts +0 -0
- /package/dist/api/{bun → sdks/bun}/proc/linux.md +0 -0
package/bin/electrobun.cjs
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const { execSync, spawn } = require('child_process');
|
|
4
|
-
const { existsSync, mkdirSync, unlinkSync, chmodSync, copyFileSync, createWriteStream } = require('fs');
|
|
5
|
-
const { join, dirname } = require('path');
|
|
6
|
-
const https = require('https');
|
|
7
|
-
const ProxyAgent = require('proxy-agent').ProxyAgent;
|
|
8
|
-
|
|
9
|
-
// Create an HTTPS agent that respects environment proxy settings
|
|
10
|
-
const agent = new ProxyAgent();
|
|
11
|
-
|
|
12
|
-
// Detect platform and architecture
|
|
13
|
-
function getPlatform() {
|
|
14
|
-
switch (process.platform) {
|
|
15
|
-
case 'win32': return 'win';
|
|
16
|
-
case 'darwin': return 'darwin';
|
|
17
|
-
case 'linux': return 'linux';
|
|
18
|
-
default: throw new Error(`Unsupported platform: ${process.platform}`);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function getArch() {
|
|
23
|
-
switch (process.arch) {
|
|
24
|
-
case 'arm64': return 'arm64';
|
|
25
|
-
case 'x64': return 'x64';
|
|
26
|
-
default: throw new Error(`Unsupported architecture: ${process.arch}`);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const platform = getPlatform();
|
|
31
|
-
// Always use x64 for Windows since we only build x64 Windows binaries
|
|
32
|
-
const arch = platform === 'win' ? 'x64' : getArch();
|
|
33
|
-
const binExt = platform === 'win' ? '.exe' : '';
|
|
34
|
-
|
|
35
|
-
function getTarCommand() {
|
|
36
|
-
if (platform !== 'win') {
|
|
37
|
-
return 'tar';
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Git Bash tar can treat C:\... as a remote path. Force the built-in Windows tar.
|
|
41
|
-
const systemTar = join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'tar.exe');
|
|
42
|
-
return existsSync(systemTar) ? `"${systemTar}"` : 'tar';
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Paths
|
|
46
|
-
const electrobunDir = join(__dirname, '..');
|
|
47
|
-
const cacheDir = join(electrobunDir, '.cache');
|
|
48
|
-
const cliBinary = join(cacheDir, `electrobun${binExt}`);
|
|
49
|
-
|
|
50
|
-
async function downloadFile(url, filePath) {
|
|
51
|
-
return new Promise((resolve, reject) => {
|
|
52
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
53
|
-
const file = createWriteStream(filePath);
|
|
54
|
-
|
|
55
|
-
https.get(url, {agent}, (response) => {
|
|
56
|
-
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
57
|
-
// Follow redirect
|
|
58
|
-
return downloadFile(response.headers.location, filePath).then(resolve).catch(reject);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (response.statusCode !== 200) {
|
|
62
|
-
reject(new Error(`Download failed: ${response.statusCode}`));
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
response.pipe(file);
|
|
67
|
-
|
|
68
|
-
file.on('finish', () => {
|
|
69
|
-
file.close();
|
|
70
|
-
resolve();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
file.on('error', reject);
|
|
74
|
-
}).on('error', reject);
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function ensureCliBinary() {
|
|
79
|
-
// Check if CLI binary exists in bin location (where npm expects it)
|
|
80
|
-
const binLocation = join(electrobunDir, 'bin', 'electrobun' + binExt);
|
|
81
|
-
if (existsSync(binLocation)) {
|
|
82
|
-
return binLocation;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Check if core dependencies already exist in cache
|
|
86
|
-
if (existsSync(cliBinary)) {
|
|
87
|
-
// Copy to bin location if it exists in cache but not in bin
|
|
88
|
-
mkdirSync(dirname(binLocation), { recursive: true });
|
|
89
|
-
copyFileSync(cliBinary, binLocation);
|
|
90
|
-
if (platform !== 'win') {
|
|
91
|
-
chmodSync(binLocation, '755');
|
|
92
|
-
}
|
|
93
|
-
return binLocation;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
console.log('Downloading electrobun CLI for your platform...');
|
|
97
|
-
|
|
98
|
-
// Get the package version to download the matching release
|
|
99
|
-
const packageJson = require(join(electrobunDir, 'package.json'));
|
|
100
|
-
const version = packageJson.version;
|
|
101
|
-
const tag = `v${version}`;
|
|
102
|
-
|
|
103
|
-
const tarballUrl = `https://github.com/blackboardsh/electrobun/releases/download/${tag}/electrobun-cli-${platform}-${arch}.tar.gz`;
|
|
104
|
-
const tarballPath = join(cacheDir, `electrobun-${platform}-${arch}.tar.gz`);
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
// Download tarball
|
|
108
|
-
await downloadFile(tarballUrl, tarballPath);
|
|
109
|
-
|
|
110
|
-
// Extract using system tar (available on macOS, Linux, and Windows 10+)
|
|
111
|
-
execSync(`${getTarCommand()} -xzf "${tarballPath}"`, { cwd: cacheDir, stdio: 'pipe' });
|
|
112
|
-
|
|
113
|
-
// Clean up tarball
|
|
114
|
-
unlinkSync(tarballPath);
|
|
115
|
-
|
|
116
|
-
// Check if CLI binary was extracted
|
|
117
|
-
if (!existsSync(cliBinary)) {
|
|
118
|
-
throw new Error(`CLI binary not found at ${cliBinary} after extraction`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Make executable on Unix systems
|
|
122
|
-
if (platform !== 'win') {
|
|
123
|
-
chmodSync(cliBinary, '755');
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Copy CLI to bin location so npm scripts can find it
|
|
127
|
-
mkdirSync(dirname(binLocation), { recursive: true });
|
|
128
|
-
copyFileSync(cliBinary, binLocation);
|
|
129
|
-
|
|
130
|
-
// Make the bin location executable too
|
|
131
|
-
if (platform !== 'win') {
|
|
132
|
-
chmodSync(binLocation, '755');
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
console.log('electrobun CLI downloaded successfully!');
|
|
136
|
-
return binLocation;
|
|
137
|
-
|
|
138
|
-
} catch (error) {
|
|
139
|
-
throw new Error(`Failed to download electrobun CLI: ${error.message}`);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async function main() {
|
|
144
|
-
try {
|
|
145
|
-
const args = process.argv.slice(2);
|
|
146
|
-
const cliPath = await ensureCliBinary();
|
|
147
|
-
|
|
148
|
-
// Replace this process with the actual CLI
|
|
149
|
-
const child = spawn(cliPath, args, {
|
|
150
|
-
stdio: 'inherit',
|
|
151
|
-
cwd: process.cwd()
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
child.on('exit', (code) => {
|
|
155
|
-
process.exit(code || 0);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
child.on('error', (error) => {
|
|
159
|
-
console.error('Failed to start electrobun CLI:', error.message);
|
|
160
|
-
process.exit(1);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
} catch (error) {
|
|
164
|
-
console.error('Error:', error.message);
|
|
165
|
-
process.exit(1);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
main();
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import type { Server, ServerWebSocket } from "bun";
|
|
2
|
-
import { BrowserView } from "./BrowserView";
|
|
3
|
-
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
4
|
-
|
|
5
|
-
function base64ToUint8Array(base64: string) {
|
|
6
|
-
{
|
|
7
|
-
return new Uint8Array(
|
|
8
|
-
atob(base64)
|
|
9
|
-
.split("")
|
|
10
|
-
.map((char) => char.charCodeAt(0)),
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Encrypt function
|
|
16
|
-
function encrypt(secretKey: Uint8Array, text: string) {
|
|
17
|
-
const iv = new Uint8Array(randomBytes(12)); // IV for AES-GCM
|
|
18
|
-
const cipher = createCipheriv("aes-256-gcm", secretKey, iv);
|
|
19
|
-
const encrypted = Buffer.concat([
|
|
20
|
-
new Uint8Array(cipher.update(text, "utf8")),
|
|
21
|
-
new Uint8Array(cipher.final()),
|
|
22
|
-
]).toString("base64");
|
|
23
|
-
const tag = cipher.getAuthTag().toString("base64");
|
|
24
|
-
return { encrypted, iv: Buffer.from(iv).toString("base64"), tag };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Decrypt function
|
|
28
|
-
function decrypt(
|
|
29
|
-
secretKey: Uint8Array,
|
|
30
|
-
encryptedData: Uint8Array,
|
|
31
|
-
iv: Uint8Array,
|
|
32
|
-
tag: Uint8Array,
|
|
33
|
-
) {
|
|
34
|
-
const decipher = createDecipheriv("aes-256-gcm", secretKey, iv);
|
|
35
|
-
decipher.setAuthTag(tag);
|
|
36
|
-
const decrypted = Buffer.concat([
|
|
37
|
-
new Uint8Array(decipher.update(encryptedData)),
|
|
38
|
-
new Uint8Array(decipher.final()),
|
|
39
|
-
]);
|
|
40
|
-
return decrypted.toString("utf8");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const socketMap: {
|
|
44
|
-
[webviewId: string]: {
|
|
45
|
-
socket: null | ServerWebSocket<unknown>;
|
|
46
|
-
queue: string[];
|
|
47
|
-
};
|
|
48
|
-
} = {};
|
|
49
|
-
|
|
50
|
-
export const removeSocketForWebview = (webviewId: number) => {
|
|
51
|
-
const rpc = socketMap[webviewId];
|
|
52
|
-
if (!rpc) return;
|
|
53
|
-
|
|
54
|
-
rpc.socket = null;
|
|
55
|
-
delete socketMap[webviewId];
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const startRPCServer = () => {
|
|
59
|
-
const startPort = 50000;
|
|
60
|
-
const endPort = 65535;
|
|
61
|
-
const payloadLimit = 1024 * 1024 * 500; // 500MB
|
|
62
|
-
let port = startPort;
|
|
63
|
-
let server = null;
|
|
64
|
-
|
|
65
|
-
while (port <= endPort) {
|
|
66
|
-
try {
|
|
67
|
-
server = Bun.serve<{ webviewId: number }>({
|
|
68
|
-
port,
|
|
69
|
-
fetch(req: Request, server: Server<{ webviewId: number }>) {
|
|
70
|
-
const url = new URL(req.url);
|
|
71
|
-
// const token = new URL(req.url).searchParams.get("token");
|
|
72
|
-
// if (token !== AUTH_TOKEN)
|
|
73
|
-
// return new Response("Unauthorized", { status: 401 });
|
|
74
|
-
// console.log("fetch!!", url.pathname);
|
|
75
|
-
if (url.pathname === "/socket") {
|
|
76
|
-
const webviewIdString = url.searchParams.get("webviewId");
|
|
77
|
-
if (!webviewIdString) {
|
|
78
|
-
return new Response("Missing webviewId", { status: 400 });
|
|
79
|
-
}
|
|
80
|
-
const webviewId = parseInt(webviewIdString, 10);
|
|
81
|
-
const success = server.upgrade(req, { data: { webviewId } });
|
|
82
|
-
return success
|
|
83
|
-
? undefined
|
|
84
|
-
: new Response("Upgrade failed", { status: 500 });
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
console.log("unhandled RPC Server request", req.url);
|
|
88
|
-
},
|
|
89
|
-
websocket: {
|
|
90
|
-
idleTimeout: 960,
|
|
91
|
-
// 500MB max payload should be plenty
|
|
92
|
-
maxPayloadLength: payloadLimit,
|
|
93
|
-
// Anything beyond the backpressure limit will be dropped
|
|
94
|
-
backpressureLimit: payloadLimit * 2,
|
|
95
|
-
open(ws: ServerWebSocket<{ webviewId: number }>) {
|
|
96
|
-
if (!ws?.data) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const { webviewId } = ws.data;
|
|
100
|
-
|
|
101
|
-
if (!socketMap[webviewId]) {
|
|
102
|
-
socketMap[webviewId] = { socket: ws, queue: [] };
|
|
103
|
-
} else {
|
|
104
|
-
socketMap[webviewId].socket = ws;
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
|
-
close(ws: ServerWebSocket<{ webviewId: number }>, _code: number, _reason: string) {
|
|
108
|
-
if (!ws?.data) {
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
const { webviewId } = ws.data;
|
|
112
|
-
// console.log("Closed:", webviewId, code, reason);
|
|
113
|
-
if (socketMap[webviewId]) {
|
|
114
|
-
socketMap[webviewId].socket = null;
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
message(ws: ServerWebSocket<{ webviewId: number }>, message: string | Buffer) {
|
|
119
|
-
if (!ws?.data) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
const { webviewId } = ws.data;
|
|
123
|
-
const browserView = BrowserView.getById(webviewId);
|
|
124
|
-
if (!browserView) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (browserView.rpcHandler) {
|
|
129
|
-
if (typeof message === "string") {
|
|
130
|
-
try {
|
|
131
|
-
const encryptedPacket = JSON.parse(message);
|
|
132
|
-
const decrypted = decrypt(
|
|
133
|
-
browserView.secretKey,
|
|
134
|
-
base64ToUint8Array(encryptedPacket.encryptedData),
|
|
135
|
-
base64ToUint8Array(encryptedPacket.iv),
|
|
136
|
-
base64ToUint8Array(encryptedPacket.tag),
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
// Note: At this point the secretKey for the webview id would
|
|
140
|
-
// have had to match the encrypted packet data, so we can trust
|
|
141
|
-
// that this message can be passed to this browserview's rpc
|
|
142
|
-
// methods.
|
|
143
|
-
browserView.rpcHandler(JSON.parse(decrypted));
|
|
144
|
-
} catch (error) {
|
|
145
|
-
console.log("Error handling message:", error);
|
|
146
|
-
}
|
|
147
|
-
} else if (message instanceof ArrayBuffer) {
|
|
148
|
-
console.log("TODO: Received ArrayBuffer message:", message);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
break;
|
|
156
|
-
} catch (error: any) {
|
|
157
|
-
if (error.code === "EADDRINUSE") {
|
|
158
|
-
console.log(`Port ${port} in use, trying next port...`);
|
|
159
|
-
port++;
|
|
160
|
-
} else {
|
|
161
|
-
throw error;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return { rpcServer: server, rpcPort: port };
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
export const { rpcServer, rpcPort } = startRPCServer();
|
|
170
|
-
|
|
171
|
-
// Will return true if message was sent over websocket
|
|
172
|
-
// false if it was not (caller should fallback to postMessage/evaluateJS rpc)
|
|
173
|
-
export const sendMessageToWebviewViaSocket = (
|
|
174
|
-
webviewId: number,
|
|
175
|
-
message: any,
|
|
176
|
-
): boolean => {
|
|
177
|
-
const rpc = socketMap[webviewId];
|
|
178
|
-
const browserView = BrowserView.getById(webviewId);
|
|
179
|
-
|
|
180
|
-
if (!browserView) return false;
|
|
181
|
-
|
|
182
|
-
if (rpc?.socket?.readyState === WebSocket.OPEN) {
|
|
183
|
-
try {
|
|
184
|
-
const unencryptedString = JSON.stringify(message);
|
|
185
|
-
const encrypted = encrypt(browserView.secretKey, unencryptedString);
|
|
186
|
-
|
|
187
|
-
const encryptedPacket = {
|
|
188
|
-
encryptedData: encrypted.encrypted,
|
|
189
|
-
iv: encrypted.iv,
|
|
190
|
-
tag: encrypted.tag,
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const encryptedPacketString = JSON.stringify(encryptedPacket);
|
|
194
|
-
|
|
195
|
-
rpc.socket.send(encryptedPacketString);
|
|
196
|
-
return true;
|
|
197
|
-
} catch (error) {
|
|
198
|
-
console.error("Error sending message to webview via socket:", error);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return false;
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
console.log("Server started at", rpcServer?.url.origin);
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
// Auto-generated file. Do not edit directly.
|
|
2
|
-
// Run "bun build.ts" or "bun build:dev" from the package folder to regenerate.
|
|
3
|
-
|
|
4
|
-
// Full preload for trusted webviews (RPC, encryption, drag regions, webview tags)
|
|
5
|
-
export const preloadScript = "(function(){// src/bun/preload/encryption.ts\nfunction base64ToUint8Array(base64) {\n return new Uint8Array(atob(base64).split(\"\").map((char) => char.charCodeAt(0)));\n}\nfunction uint8ArrayToBase64(uint8Array) {\n let binary = \"\";\n for (let i = 0;i < uint8Array.length; i++) {\n binary += String.fromCharCode(uint8Array[i]);\n }\n return btoa(binary);\n}\nasync function generateKeyFromBytes(rawKey) {\n return await window.crypto.subtle.importKey(\"raw\", rawKey, { name: \"AES-GCM\" }, true, [\"encrypt\", \"decrypt\"]);\n}\nasync function initEncryption() {\n const secretKey = await generateKeyFromBytes(new Uint8Array(window.__electrobunSecretKeyBytes));\n const encryptString = async (plaintext) => {\n const encoder = new TextEncoder;\n const encodedText = encoder.encode(plaintext);\n const iv = window.crypto.getRandomValues(new Uint8Array(12));\n const encryptedBuffer = await window.crypto.subtle.encrypt({ name: \"AES-GCM\", iv }, secretKey, encodedText);\n const encryptedData = new Uint8Array(encryptedBuffer.slice(0, -16));\n const tag = new Uint8Array(encryptedBuffer.slice(-16));\n return {\n encryptedData: uint8ArrayToBase64(encryptedData),\n iv: uint8ArrayToBase64(iv),\n tag: uint8ArrayToBase64(tag)\n };\n };\n const decryptString = async (encryptedDataB64, ivB64, tagB64) => {\n const encryptedData = base64ToUint8Array(encryptedDataB64);\n const iv = base64ToUint8Array(ivB64);\n const tag = base64ToUint8Array(tagB64);\n const combinedData = new Uint8Array(encryptedData.length + tag.length);\n combinedData.set(encryptedData);\n combinedData.set(tag, encryptedData.length);\n const decryptedBuffer = await window.crypto.subtle.decrypt({ name: \"AES-GCM\", iv }, secretKey, combinedData);\n const decoder = new TextDecoder;\n return decoder.decode(decryptedBuffer);\n };\n window.__electrobun_encrypt = encryptString;\n window.__electrobun_decrypt = decryptString;\n}\n\n// src/bun/preload/internalRpc.ts\nvar pendingRequests = {};\nvar requestId = 0;\nvar isProcessingQueue = false;\nvar sendQueue = [];\nfunction processQueue() {\n if (isProcessingQueue) {\n setTimeout(processQueue);\n return;\n }\n if (sendQueue.length === 0)\n return;\n isProcessingQueue = true;\n const batch = JSON.stringify(sendQueue);\n sendQueue.length = 0;\n window.__electrobunInternalBridge?.postMessage(batch);\n setTimeout(() => {\n isProcessingQueue = false;\n }, 2);\n}\nfunction send(type, payload) {\n sendQueue.push(JSON.stringify({ type: \"message\", id: type, payload }));\n processQueue();\n}\nfunction request(type, payload) {\n return new Promise((resolve, reject) => {\n const id = `req_${++requestId}_${Date.now()}`;\n pendingRequests[id] = { resolve, reject };\n sendQueue.push(JSON.stringify({\n type: \"request\",\n method: type,\n id,\n params: payload,\n hostWebviewId: window.__electrobunWebviewId\n }));\n processQueue();\n setTimeout(() => {\n if (pendingRequests[id]) {\n delete pendingRequests[id];\n reject(new Error(`Request timeout: ${type}`));\n }\n }, 1e4);\n });\n}\nfunction handleResponse(msg) {\n if (msg && msg.type === \"response\" && msg.id) {\n const pending = pendingRequests[msg.id];\n if (pending) {\n delete pendingRequests[msg.id];\n if (msg.success)\n pending.resolve(msg.payload);\n else\n pending.reject(msg.payload);\n }\n }\n}\n\n// src/bun/preload/dragRegions.ts\nfunction isAppRegionDrag(e) {\n const target = e.target;\n if (!target || !target.closest)\n return false;\n if (target.closest(\".electrobun-webkit-app-region-no-drag\") || target.closest('[style*=\"app-region\"][style*=\"no-drag\"]')) {\n return false;\n }\n const draggableByStyle = target.closest('[style*=\"app-region\"][style*=\"drag\"]');\n const draggableByClass = target.closest(\".electrobun-webkit-app-region-drag\");\n return !!(draggableByStyle || draggableByClass);\n}\nfunction initDragRegions() {\n document.addEventListener(\"mousedown\", (e) => {\n if (isAppRegionDrag(e)) {\n send(\"startWindowMove\", { id: window.__electrobunWindowId });\n }\n });\n document.addEventListener(\"mouseup\", (e) => {\n if (isAppRegionDrag(e)) {\n send(\"stopWindowMove\", { id: window.__electrobunWindowId });\n }\n });\n}\n\n// src/bun/preload/overlaySync.ts\nclass OverlaySyncController {\n element;\n options;\n lastRect = { x: 0, y: 0, width: 0, height: 0 };\n resizeObserver = null;\n positionLoop = null;\n resizeHandler = null;\n burstUntil = 0;\n constructor(element, options) {\n this.element = element;\n this.options = {\n onSync: options.onSync,\n getMasks: options.getMasks ?? (() => []),\n burstIntervalMs: options.burstIntervalMs ?? 50,\n baseIntervalMs: options.baseIntervalMs ?? 100,\n burstDurationMs: options.burstDurationMs ?? 500\n };\n }\n start() {\n this.resizeObserver = new ResizeObserver(() => this.sync());\n this.resizeObserver.observe(this.element);\n const loop = () => {\n this.sync();\n const now = performance.now();\n const interval = now < this.burstUntil ? this.options.burstIntervalMs : this.options.baseIntervalMs;\n this.positionLoop = setTimeout(loop, interval);\n };\n this.positionLoop = setTimeout(loop, this.options.baseIntervalMs);\n this.resizeHandler = () => this.sync(true);\n window.addEventListener(\"resize\", this.resizeHandler);\n }\n stop() {\n if (this.resizeObserver)\n this.resizeObserver.disconnect();\n if (this.positionLoop)\n clearTimeout(this.positionLoop);\n if (this.resizeHandler) {\n window.removeEventListener(\"resize\", this.resizeHandler);\n }\n this.resizeObserver = null;\n this.positionLoop = null;\n this.resizeHandler = null;\n }\n forceSync() {\n this.sync(true);\n }\n setLastRect(rect) {\n this.lastRect = rect;\n }\n sync(force = false) {\n const rect = this.element.getBoundingClientRect();\n const newRect = {\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height\n };\n if (newRect.width === 0 && newRect.height === 0) {\n return;\n }\n if (!force && newRect.x === this.lastRect.x && newRect.y === this.lastRect.y && newRect.width === this.lastRect.width && newRect.height === this.lastRect.height) {\n return;\n }\n this.burstUntil = performance.now() + this.options.burstDurationMs;\n this.lastRect = newRect;\n const masks = this.options.getMasks();\n this.options.onSync(newRect, JSON.stringify(masks));\n }\n}\n\n// src/bun/preload/webviewTag.ts\nvar webviewRegistry = {};\n\nclass ElectrobunWebviewTag extends HTMLElement {\n webviewId = null;\n maskSelectors = new Set;\n _sync = null;\n transparent = false;\n passthroughEnabled = false;\n hidden = false;\n sandboxed = false;\n _eventListeners = {};\n static get observedAttributes() {\n return [\"src\", \"html\"];\n }\n constructor() {\n super();\n }\n connectedCallback() {\n requestAnimationFrame(() => this.initWebview());\n }\n attributeChangedCallback(name, oldValue, newValue) {\n if (oldValue === newValue)\n return;\n if (newValue === null)\n return;\n if (this.webviewId === null)\n return;\n if (name === \"src\")\n this.loadURL(newValue);\n else if (name === \"html\")\n this.loadHTML(newValue);\n }\n disconnectedCallback() {\n if (this.webviewId !== null) {\n send(\"webviewTagRemove\", { id: this.webviewId });\n delete webviewRegistry[this.webviewId];\n }\n if (this._sync)\n this._sync.stop();\n }\n async initWebview() {\n const rect = this.getBoundingClientRect();\n const initialRect = {\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height\n };\n const url = this.getAttribute(\"src\");\n const html = this.getAttribute(\"html\");\n const preload = this.getAttribute(\"preload\");\n const partition = this.getAttribute(\"partition\");\n const renderer = this.getAttribute(\"renderer\") || \"native\";\n const masks = this.getAttribute(\"masks\");\n const sandbox = this.hasAttribute(\"sandbox\");\n this.sandboxed = sandbox;\n const transparent = this.hasAttribute(\"transparent\");\n const passthrough = this.hasAttribute(\"passthrough\");\n this.transparent = transparent;\n this.passthroughEnabled = passthrough;\n if (transparent)\n this.style.opacity = \"0\";\n if (passthrough)\n this.style.pointerEvents = \"none\";\n if (masks) {\n masks.split(\",\").forEach((s) => this.maskSelectors.add(s.trim()));\n }\n try {\n const webviewId = await request(\"webviewTagInit\", {\n hostWebviewId: window.__electrobunWebviewId,\n windowId: window.__electrobunWindowId,\n renderer,\n url,\n html,\n preload,\n partition,\n frame: {\n width: rect.width,\n height: rect.height,\n x: rect.x,\n y: rect.y\n },\n navigationRules: null,\n sandbox,\n transparent,\n passthrough\n });\n this.webviewId = webviewId;\n this.id = `electrobun-webview-${webviewId}`;\n webviewRegistry[webviewId] = this;\n this.setupObservers(initialRect);\n this.syncDimensions(true);\n requestAnimationFrame(() => {\n Object.values(webviewRegistry).forEach((webview) => {\n if (webview !== this && webview.webviewId !== null) {\n webview.syncDimensions(true);\n }\n });\n });\n } catch (err) {\n console.error(\"Failed to init webview:\", err);\n }\n }\n setupObservers(initialRect) {\n const getMasks = () => {\n const rect = this.getBoundingClientRect();\n const masks = [];\n this.maskSelectors.forEach((selector) => {\n try {\n document.querySelectorAll(selector).forEach((el) => {\n const mr = el.getBoundingClientRect();\n masks.push({\n x: mr.x - rect.x,\n y: mr.y - rect.y,\n width: mr.width,\n height: mr.height\n });\n });\n } catch (_e) {}\n });\n return masks;\n };\n this._sync = new OverlaySyncController(this, {\n onSync: (rect, masksJson) => {\n if (this.webviewId === null)\n return;\n send(\"webviewTagResize\", {\n id: this.webviewId,\n frame: rect,\n masks: masksJson\n });\n },\n getMasks,\n burstIntervalMs: 10,\n baseIntervalMs: 100,\n burstDurationMs: 50\n });\n this._sync.setLastRect(initialRect);\n this._sync.start();\n }\n syncDimensions(force = false) {\n if (!this._sync)\n return;\n if (force) {\n this._sync.forceSync();\n }\n }\n loadURL(url) {\n if (this.webviewId === null)\n return;\n this.setAttribute(\"src\", url);\n send(\"webviewTagUpdateSrc\", { id: this.webviewId, url });\n }\n loadHTML(html) {\n if (this.webviewId === null)\n return;\n send(\"webviewTagUpdateHtml\", { id: this.webviewId, html });\n }\n reload() {\n if (this.webviewId !== null)\n send(\"webviewTagReload\", { id: this.webviewId });\n }\n goBack() {\n if (this.webviewId !== null)\n send(\"webviewTagGoBack\", { id: this.webviewId });\n }\n goForward() {\n if (this.webviewId !== null)\n send(\"webviewTagGoForward\", { id: this.webviewId });\n }\n async canGoBack() {\n if (this.webviewId === null)\n return false;\n return await request(\"webviewTagCanGoBack\", {\n id: this.webviewId\n });\n }\n async canGoForward() {\n if (this.webviewId === null)\n return false;\n return await request(\"webviewTagCanGoForward\", {\n id: this.webviewId\n });\n }\n toggleTransparent(value) {\n if (this.webviewId === null)\n return;\n this.transparent = value !== undefined ? value : !this.transparent;\n this.style.opacity = this.transparent ? \"0\" : \"\";\n send(\"webviewTagSetTransparent\", {\n id: this.webviewId,\n transparent: this.transparent\n });\n }\n togglePassthrough(value) {\n if (this.webviewId === null)\n return;\n this.passthroughEnabled = value !== undefined ? value : !this.passthroughEnabled;\n this.style.pointerEvents = this.passthroughEnabled ? \"none\" : \"\";\n send(\"webviewTagSetPassthrough\", {\n id: this.webviewId,\n enablePassthrough: this.passthroughEnabled\n });\n }\n toggleHidden(value) {\n if (this.webviewId === null)\n return;\n this.hidden = value !== undefined ? value : !this.hidden;\n send(\"webviewTagSetHidden\", { id: this.webviewId, hidden: this.hidden });\n }\n addMaskSelector(selector) {\n this.maskSelectors.add(selector);\n this.syncDimensions(true);\n }\n removeMaskSelector(selector) {\n this.maskSelectors.delete(selector);\n this.syncDimensions(true);\n }\n setNavigationRules(rules) {\n if (this.webviewId !== null) {\n send(\"webviewTagSetNavigationRules\", { id: this.webviewId, rules });\n }\n }\n findInPage(searchText, options) {\n if (this.webviewId === null)\n return;\n const forward = options?.forward !== false;\n const matchCase = options?.matchCase || false;\n send(\"webviewTagFindInPage\", {\n id: this.webviewId,\n searchText,\n forward,\n matchCase\n });\n }\n stopFindInPage() {\n if (this.webviewId !== null)\n send(\"webviewTagStopFind\", { id: this.webviewId });\n }\n openDevTools() {\n if (this.webviewId !== null)\n send(\"webviewTagOpenDevTools\", { id: this.webviewId });\n }\n closeDevTools() {\n if (this.webviewId !== null)\n send(\"webviewTagCloseDevTools\", { id: this.webviewId });\n }\n toggleDevTools() {\n if (this.webviewId !== null)\n send(\"webviewTagToggleDevTools\", { id: this.webviewId });\n }\n executeJavascript(js) {\n if (this.webviewId === null)\n return;\n send(\"webviewTagExecuteJavascript\", { id: this.webviewId, js });\n }\n on(event, listener) {\n if (!this._eventListeners[event])\n this._eventListeners[event] = [];\n this._eventListeners[event].push(listener);\n }\n off(event, listener) {\n if (!this._eventListeners[event])\n return;\n const idx = this._eventListeners[event].indexOf(listener);\n if (idx !== -1)\n this._eventListeners[event].splice(idx, 1);\n }\n emit(event, detail) {\n const listeners = this._eventListeners[event];\n if (listeners) {\n const customEvent = new CustomEvent(event, { detail });\n listeners.forEach((fn) => fn(customEvent));\n }\n }\n get src() {\n return this.getAttribute(\"src\");\n }\n set src(value) {\n if (value) {\n this.setAttribute(\"src\", value);\n } else {\n this.removeAttribute(\"src\");\n }\n }\n get html() {\n return this.getAttribute(\"html\");\n }\n set html(value) {\n if (value) {\n this.setAttribute(\"html\", value);\n } else {\n this.removeAttribute(\"html\");\n }\n }\n get preload() {\n return this.getAttribute(\"preload\");\n }\n set preload(value) {\n if (value)\n this.setAttribute(\"preload\", value);\n else\n this.removeAttribute(\"preload\");\n }\n get renderer() {\n return this.getAttribute(\"renderer\") || \"native\";\n }\n set renderer(value) {\n this.setAttribute(\"renderer\", value);\n }\n get sandbox() {\n return this.sandboxed;\n }\n}\nfunction initWebviewTag() {\n if (!customElements.get(\"electrobun-webview\")) {\n customElements.define(\"electrobun-webview\", ElectrobunWebviewTag);\n }\n const injectStyles = () => {\n const style = document.createElement(\"style\");\n style.textContent = `\nelectrobun-webview {\n\tdisplay: block;\n\twidth: 800px;\n\theight: 300px;\n\tbackground: #fff;\n\tbackground-repeat: no-repeat !important;\n\toverflow: hidden;\n}\n`;\n if (document.head?.firstChild) {\n document.head.insertBefore(style, document.head.firstChild);\n } else if (document.head) {\n document.head.appendChild(style);\n }\n };\n if (document.head) {\n injectStyles();\n } else {\n document.addEventListener(\"DOMContentLoaded\", injectStyles);\n }\n}\n\n// src/bun/preload/wgpuTag.ts\nvar wgpuTagRegistry = {};\n\nclass ElectrobunWgpuTag extends HTMLElement {\n wgpuViewId = null;\n maskSelectors = new Set;\n _sync = null;\n transparent = false;\n passthroughEnabled = false;\n hidden = false;\n _eventListeners = {};\n constructor() {\n super();\n }\n connectedCallback() {\n requestAnimationFrame(() => this.initWgpuView());\n }\n disconnectedCallback() {\n if (this.wgpuViewId !== null) {\n send(\"wgpuTagRemove\", { id: this.wgpuViewId });\n delete wgpuTagRegistry[this.wgpuViewId];\n }\n if (this._sync)\n this._sync.stop();\n }\n async initWgpuView() {\n const rect = this.getBoundingClientRect();\n const initialRect = {\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height\n };\n const transparent = this.hasAttribute(\"transparent\");\n const passthrough = this.hasAttribute(\"passthrough\");\n const hidden = this.hasAttribute(\"hidden\");\n const masks = this.getAttribute(\"masks\");\n this.transparent = transparent;\n this.passthroughEnabled = passthrough;\n this.hidden = hidden;\n if (masks) {\n masks.split(\",\").forEach((s) => this.maskSelectors.add(s.trim()));\n }\n if (transparent)\n this.style.opacity = \"0\";\n if (passthrough)\n this.style.pointerEvents = \"none\";\n try {\n const wgpuViewId = await request(\"wgpuTagInit\", {\n windowId: window.__electrobunWindowId,\n frame: {\n width: rect.width,\n height: rect.height,\n x: rect.x,\n y: rect.y\n },\n transparent,\n passthrough\n });\n this.wgpuViewId = wgpuViewId;\n this.id = `electrobun-wgpu-${wgpuViewId}`;\n wgpuTagRegistry[wgpuViewId] = this;\n this.setupObservers(initialRect);\n this.syncDimensions(true);\n if (hidden) {\n this.toggleHidden(true);\n }\n requestAnimationFrame(() => {\n Object.values(wgpuTagRegistry).forEach((view) => {\n if (view !== this && view.wgpuViewId !== null) {\n view.syncDimensions(true);\n }\n });\n });\n this.emit(\"ready\", { id: wgpuViewId });\n } catch (err) {\n console.error(\"Failed to init WGPU view:\", err);\n }\n }\n setupObservers(initialRect) {\n const getMasks = () => {\n const rect = this.getBoundingClientRect();\n const masks = [];\n this.maskSelectors.forEach((selector) => {\n try {\n document.querySelectorAll(selector).forEach((el) => {\n const mr = el.getBoundingClientRect();\n masks.push({\n x: mr.x - rect.x,\n y: mr.y - rect.y,\n width: mr.width,\n height: mr.height\n });\n });\n } catch (_e) {}\n });\n return masks;\n };\n this._sync = new OverlaySyncController(this, {\n onSync: (rect, masksJson) => {\n if (this.wgpuViewId === null)\n return;\n send(\"wgpuTagResize\", {\n id: this.wgpuViewId,\n frame: rect,\n masks: masksJson\n });\n },\n getMasks,\n burstIntervalMs: 10,\n baseIntervalMs: 100,\n burstDurationMs: 50\n });\n this._sync.setLastRect(initialRect);\n this._sync.start();\n }\n syncDimensions(force = false) {\n if (!this._sync)\n return;\n if (force) {\n this._sync.forceSync();\n }\n }\n toggleTransparent(value) {\n if (this.wgpuViewId === null)\n return;\n this.transparent = value !== undefined ? value : !this.transparent;\n this.style.opacity = this.transparent ? \"0\" : \"\";\n send(\"wgpuTagSetTransparent\", {\n id: this.wgpuViewId,\n transparent: this.transparent\n });\n }\n togglePassthrough(value) {\n if (this.wgpuViewId === null)\n return;\n this.passthroughEnabled = value !== undefined ? value : !this.passthroughEnabled;\n this.style.pointerEvents = this.passthroughEnabled ? \"none\" : \"\";\n send(\"wgpuTagSetPassthrough\", {\n id: this.wgpuViewId,\n passthrough: this.passthroughEnabled\n });\n }\n toggleHidden(value) {\n if (this.wgpuViewId === null)\n return;\n this.hidden = value !== undefined ? value : !this.hidden;\n send(\"wgpuTagSetHidden\", { id: this.wgpuViewId, hidden: this.hidden });\n }\n runTest() {\n if (this.wgpuViewId === null)\n return;\n send(\"wgpuTagRunTest\", { id: this.wgpuViewId });\n }\n addMaskSelector(selector) {\n this.maskSelectors.add(selector);\n this.syncDimensions(true);\n }\n removeMaskSelector(selector) {\n this.maskSelectors.delete(selector);\n this.syncDimensions(true);\n }\n on(event, listener) {\n if (!this._eventListeners[event])\n this._eventListeners[event] = [];\n this._eventListeners[event].push(listener);\n }\n off(event, listener) {\n if (!this._eventListeners[event])\n return;\n const idx = this._eventListeners[event].indexOf(listener);\n if (idx !== -1)\n this._eventListeners[event].splice(idx, 1);\n }\n emit(event, detail) {\n const listeners = this._eventListeners[event];\n if (listeners) {\n const customEvent = new CustomEvent(event, { detail });\n listeners.forEach((fn) => fn(customEvent));\n }\n }\n}\nfunction initWgpuTag() {\n if (!customElements.get(\"electrobun-wgpu\")) {\n customElements.define(\"electrobun-wgpu\", ElectrobunWgpuTag);\n }\n const injectStyles = () => {\n const style = document.createElement(\"style\");\n style.textContent = `\nelectrobun-wgpu {\n\tdisplay: block;\n\twidth: 800px;\n\theight: 300px;\n\tbackground: #000;\n\toverflow: hidden;\n}\n`;\n if (document.head?.firstChild) {\n document.head.insertBefore(style, document.head.firstChild);\n } else if (document.head) {\n document.head.appendChild(style);\n }\n };\n if (document.head) {\n injectStyles();\n } else {\n document.addEventListener(\"DOMContentLoaded\", injectStyles);\n }\n}\n\n// src/bun/preload/events.ts\nfunction emitWebviewEvent(eventName, detail) {\n setTimeout(() => {\n const bridge = window.__electrobunEventBridge || window.__electrobunInternalBridge;\n bridge?.postMessage(JSON.stringify({\n id: \"webviewEvent\",\n type: \"message\",\n payload: {\n id: window.__electrobunWebviewId,\n eventName,\n detail\n }\n }));\n });\n}\nfunction initLifecycleEvents() {\n window.addEventListener(\"load\", () => {\n if (window === window.top) {\n emitWebviewEvent(\"dom-ready\", document.location.href);\n }\n });\n window.addEventListener(\"popstate\", () => {\n emitWebviewEvent(\"did-navigate-in-page\", window.location.href);\n });\n window.addEventListener(\"hashchange\", () => {\n emitWebviewEvent(\"did-navigate-in-page\", window.location.href);\n });\n}\nvar cmdKeyHeld = false;\nvar cmdKeyTimestamp = 0;\nvar CMD_KEY_THRESHOLD_MS = 500;\nfunction isCmdHeld() {\n if (cmdKeyHeld)\n return true;\n return Date.now() - cmdKeyTimestamp < CMD_KEY_THRESHOLD_MS && cmdKeyTimestamp > 0;\n}\nfunction initCmdClickHandling() {\n window.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Meta\" || event.metaKey) {\n cmdKeyHeld = true;\n cmdKeyTimestamp = Date.now();\n }\n }, true);\n window.addEventListener(\"keyup\", (event) => {\n if (event.key === \"Meta\") {\n cmdKeyHeld = false;\n cmdKeyTimestamp = Date.now();\n }\n }, true);\n window.addEventListener(\"blur\", () => {\n cmdKeyHeld = false;\n });\n window.addEventListener(\"click\", (event) => {\n if (event.metaKey || event.ctrlKey) {\n const anchor = event.target?.closest?.(\"a\");\n if (anchor && anchor.href) {\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: anchor.href,\n isCmdClick: true,\n isSPANavigation: false\n }));\n }\n }\n }, true);\n}\nfunction initSPANavigationInterception() {\n const originalPushState = history.pushState;\n const originalReplaceState = history.replaceState;\n history.pushState = function(state, title, url) {\n if (isCmdHeld() && url) {\n const resolvedUrl = new URL(String(url), window.location.href).href;\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: resolvedUrl,\n isCmdClick: true,\n isSPANavigation: true\n }));\n return;\n }\n return originalPushState.apply(this, [state, title, url]);\n };\n history.replaceState = function(state, title, url) {\n if (isCmdHeld() && url) {\n const resolvedUrl = new URL(String(url), window.location.href).href;\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: resolvedUrl,\n isCmdClick: true,\n isSPANavigation: true\n }));\n return;\n }\n return originalReplaceState.apply(this, [state, title, url]);\n };\n}\nfunction initOverscrollPrevention() {\n document.addEventListener(\"DOMContentLoaded\", () => {\n const style = document.createElement(\"style\");\n style.type = \"text/css\";\n style.appendChild(document.createTextNode(\"html, body { overscroll-behavior: none; }\"));\n document.head.appendChild(style);\n });\n}\n\n// src/bun/preload/index.ts\ninitEncryption().catch((err) => console.error(\"Failed to initialize encryption:\", err));\nvar internalMessageHandler = (msg) => {\n handleResponse(msg);\n};\nif (!window.__electrobun) {\n window.__electrobun = {\n receiveInternalMessageFromBun: internalMessageHandler,\n receiveMessageFromBun: (msg) => {\n console.log(\"receiveMessageFromBun (no handler):\", msg);\n }\n };\n} else {\n window.__electrobun.receiveInternalMessageFromBun = internalMessageHandler;\n window.__electrobun.receiveMessageFromBun = (msg) => {\n console.log(\"receiveMessageFromBun (no handler):\", msg);\n };\n}\nwindow.__electrobunSendToHost = (message) => {\n emitWebviewEvent(\"host-message\", JSON.stringify(message));\n};\ninitLifecycleEvents();\ninitCmdClickHandling();\ninitSPANavigationInterception();\ninitOverscrollPrevention();\ninitDragRegions();\ninitWebviewTag();\ninitWgpuTag();\n})();";
|
|
6
|
-
|
|
7
|
-
// Minimal preload for sandboxed/untrusted webviews (lifecycle events only, no RPC)
|
|
8
|
-
export const preloadScriptSandboxed = "(function(){// src/bun/preload/events.ts\nfunction emitWebviewEvent(eventName, detail) {\n setTimeout(() => {\n const bridge = window.__electrobunEventBridge || window.__electrobunInternalBridge;\n bridge?.postMessage(JSON.stringify({\n id: \"webviewEvent\",\n type: \"message\",\n payload: {\n id: window.__electrobunWebviewId,\n eventName,\n detail\n }\n }));\n });\n}\nfunction initLifecycleEvents() {\n window.addEventListener(\"load\", () => {\n if (window === window.top) {\n emitWebviewEvent(\"dom-ready\", document.location.href);\n }\n });\n window.addEventListener(\"popstate\", () => {\n emitWebviewEvent(\"did-navigate-in-page\", window.location.href);\n });\n window.addEventListener(\"hashchange\", () => {\n emitWebviewEvent(\"did-navigate-in-page\", window.location.href);\n });\n}\nvar cmdKeyHeld = false;\nvar cmdKeyTimestamp = 0;\nvar CMD_KEY_THRESHOLD_MS = 500;\nfunction isCmdHeld() {\n if (cmdKeyHeld)\n return true;\n return Date.now() - cmdKeyTimestamp < CMD_KEY_THRESHOLD_MS && cmdKeyTimestamp > 0;\n}\nfunction initCmdClickHandling() {\n window.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Meta\" || event.metaKey) {\n cmdKeyHeld = true;\n cmdKeyTimestamp = Date.now();\n }\n }, true);\n window.addEventListener(\"keyup\", (event) => {\n if (event.key === \"Meta\") {\n cmdKeyHeld = false;\n cmdKeyTimestamp = Date.now();\n }\n }, true);\n window.addEventListener(\"blur\", () => {\n cmdKeyHeld = false;\n });\n window.addEventListener(\"click\", (event) => {\n if (event.metaKey || event.ctrlKey) {\n const anchor = event.target?.closest?.(\"a\");\n if (anchor && anchor.href) {\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: anchor.href,\n isCmdClick: true,\n isSPANavigation: false\n }));\n }\n }\n }, true);\n}\nfunction initSPANavigationInterception() {\n const originalPushState = history.pushState;\n const originalReplaceState = history.replaceState;\n history.pushState = function(state, title, url) {\n if (isCmdHeld() && url) {\n const resolvedUrl = new URL(String(url), window.location.href).href;\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: resolvedUrl,\n isCmdClick: true,\n isSPANavigation: true\n }));\n return;\n }\n return originalPushState.apply(this, [state, title, url]);\n };\n history.replaceState = function(state, title, url) {\n if (isCmdHeld() && url) {\n const resolvedUrl = new URL(String(url), window.location.href).href;\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: resolvedUrl,\n isCmdClick: true,\n isSPANavigation: true\n }));\n return;\n }\n return originalReplaceState.apply(this, [state, title, url]);\n };\n}\nfunction initOverscrollPrevention() {\n document.addEventListener(\"DOMContentLoaded\", () => {\n const style = document.createElement(\"style\");\n style.type = \"text/css\";\n style.appendChild(document.createTextNode(\"html, body { overscroll-behavior: none; }\"));\n document.head.appendChild(style);\n });\n}\n\n// src/bun/preload/index-sandboxed.ts\ninitLifecycleEvents();\ninitCmdClickHandling();\ninitSPANavigationInterception();\ninitOverscrollPrevention();\n})();";
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|