sparkbun 0.1.4 → 0.1.6
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/dist-linux-arm64/libNativeWrapper.so +0 -0
- package/dist-linux-arm64/{libElectrobunCore.so → libSparkBunCore.so} +0 -0
- package/dist-linux-x64/libNativeWrapper.so +0 -0
- package/dist-linux-x64/{libElectrobunCore.so → libSparkBunCore.so} +0 -0
- package/dist-macos-arm64/libNativeWrapper.dylib +0 -0
- package/dist-macos-arm64/libSparkBunCore.dylib +0 -0
- package/dist-macos-x64/libNativeWrapper.dylib +0 -0
- package/dist-macos-x64/libSparkBunCore.dylib +0 -0
- package/dist-macos-x64/libasar.dylib +0 -0
- package/dist-macos-x64/libwebgpu_dawn.dylib +0 -0
- package/dist-macos-x64/process_helper +0 -0
- package/dist-win-arm64/SparkBunCore.dll +0 -0
- package/dist-win-arm64/WebView2Loader.dll +0 -0
- package/dist-win-arm64/libNativeWrapper.dll +0 -0
- package/dist-win-arm64/zig-asar/arm64/libasar.dll +0 -0
- package/dist-win-arm64/zig-asar/x64/libasar.dll +0 -0
- package/dist-win-x64/SparkBunCore.dll +0 -0
- package/package.json +1 -1
- package/scripts/setup-win-arm64.ps1 +19 -0
- package/src/browser/global.d.ts +11 -11
- package/src/browser/index.ts +10 -10
- package/src/bun/core/BrowserView.ts +2 -2
- package/src/bun/core/Updater.ts +6 -56
- package/src/bun/preload/.generated/compiled.ts +2 -2
- package/src/bun/preload/dragRegions.ts +2 -2
- package/src/bun/preload/encryption.ts +4 -4
- package/src/bun/preload/events.ts +4 -4
- package/src/bun/preload/globals.d.ts +14 -14
- package/src/bun/preload/index-sandboxed.ts +3 -3
- package/src/bun/preload/index.ts +19 -19
- package/src/bun/preload/internalRpc.ts +3 -3
- package/src/bun/preload/webviewTag.ts +2 -2
- package/src/bun/preload/wgpuTag.ts +1 -1
- package/src/bun/proc/native.ts +6 -6
- package/src/cli/index.ts +47 -83
- package/src/core/build.zig +1 -1
- package/src/core/main.zig +25 -24
- package/src/installer/installer-template.ts +1 -1
- package/src/launcher/main.ts +10 -10
- package/src/native/linux/cef_process_helper_linux.cpp +4 -4
- package/src/native/linux/nativeWrapper.cpp +230 -230
- package/src/native/macos/cef_process_helper_mac.cc +4 -4
- package/src/native/macos/nativeWrapper.mm +100 -100
- package/src/native/shared/accelerator_parser.h +5 -5
- package/src/native/shared/app_paths.h +7 -7
- package/src/native/shared/asar.h +5 -5
- package/src/native/shared/cache_migration.h +14 -14
- package/src/native/shared/callbacks.h +5 -5
- package/src/native/shared/cef_response_filter.h +18 -18
- package/src/native/shared/chromium_flags.h +6 -6
- package/src/native/shared/config.h +5 -5
- package/src/native/shared/download_event.h +5 -5
- package/src/native/shared/ffi_helpers.h +6 -6
- package/src/native/shared/glob_match.h +5 -5
- package/src/native/shared/json_menu_parser.h +5 -5
- package/src/native/shared/mime_types.h +5 -5
- package/src/native/shared/navigation_rules.h +5 -5
- package/src/native/shared/partition_context.h +5 -5
- package/src/native/shared/permissions.h +5 -5
- package/src/native/shared/permissions_cef.h +5 -5
- package/src/native/shared/preload_script.h +5 -5
- package/src/native/shared/shutdown_guard.h +5 -5
- package/src/native/shared/thread_safe_map.h +5 -5
- package/src/native/shared/webview_storage.h +5 -5
- package/src/native/win/cef_process_helper_win.cpp +4 -4
- package/src/native/win/dcomp_compositor.h +1 -1
- package/src/native/win/nativeWrapper.cpp +288 -288
- package/src/shared/bsdiff.ts +236 -0
- package/src/shared/bspatch.ts +98 -0
- package/dist-linux-arm64/bsdiff +0 -0
- package/dist-linux-arm64/bspatch +0 -0
- package/dist-linux-x64/bsdiff +0 -0
- package/dist-linux-x64/bspatch +0 -0
- package/dist-macos-arm64/bsdiff +0 -0
- package/dist-macos-arm64/bspatch +0 -0
- package/dist-macos-arm64/libElectrobunCore.dylib +0 -0
- package/dist-macos-arm64/preload-full.js +0 -885
- package/dist-macos-arm64/preload-sandboxed.js +0 -111
- package/dist-win-x64/ElectrobunCore.dll +0 -0
- package/dist-win-x64/bsdiff.exe +0 -0
- package/dist-win-x64/bspatch.exe +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Run as Administrator on Windows ARM64 Parallels VM
|
|
2
|
+
# Installs OpenSSH Server and VS Build Tools for native library compilation
|
|
3
|
+
|
|
4
|
+
# Install OpenSSH Server
|
|
5
|
+
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
|
|
6
|
+
Start-Service sshd
|
|
7
|
+
Set-Service -Name sshd -StartupType Automatic
|
|
8
|
+
|
|
9
|
+
# Allow SSH through firewall
|
|
10
|
+
New-NetFirewallRule -Name "OpenSSH-Server" -DisplayName "OpenSSH Server" -Direction Inbound -Protocol TCP -LocalPort 22 -Action Allow -ErrorAction SilentlyContinue
|
|
11
|
+
|
|
12
|
+
Write-Host "SSH server installed and running."
|
|
13
|
+
Write-Host "Connect with: ssh $env:USERNAME@<vm-ip>"
|
|
14
|
+
Write-Host ""
|
|
15
|
+
Write-Host "Next: install VS Build Tools with C++ ARM64 support."
|
|
16
|
+
Write-Host "Run this in a browser or with curl:"
|
|
17
|
+
Write-Host " https://aka.ms/vs/17/release/vs_BuildTools.exe"
|
|
18
|
+
Write-Host ""
|
|
19
|
+
Write-Host "Install with: vs_BuildTools.exe --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.VC.Tools.ARM64 --includeRecommended --passive"
|
package/src/browser/global.d.ts
CHANGED
|
@@ -19,17 +19,17 @@ interface MessageHandler {
|
|
|
19
19
|
|
|
20
20
|
declare global {
|
|
21
21
|
interface Window {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
__sparkbunWebviewId: number;
|
|
23
|
+
__sparkbunWindowId: number;
|
|
24
|
+
__sparkbunRpcSocketPort: number;
|
|
25
|
+
__sparkbunHostSocketPort?: number;
|
|
26
|
+
__sparkbun?: SparkBunBridge;
|
|
27
|
+
__sparkbunPendingHostMessages?: unknown[];
|
|
28
|
+
__sparkbun_encrypt: (msg: string) => Promise<SparkBunEncryptResult>;
|
|
29
|
+
__sparkbun_decrypt: (encryptedData: string, iv: string, tag: string) => Promise<string>;
|
|
30
|
+
__sparkbunInternalBridge?: MessageHandler;
|
|
31
|
+
__sparkbunHostBridge?: MessageHandler;
|
|
32
|
+
__sparkbunBunBridge?: MessageHandler;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
package/src/browser/index.ts
CHANGED
|
@@ -14,9 +14,9 @@ import {
|
|
|
14
14
|
import { type WgpuTagElement, type WgpuEventTypes } from "./wgputag";
|
|
15
15
|
import "./global.d.ts";
|
|
16
16
|
|
|
17
|
-
const WEBVIEW_ID = window.
|
|
17
|
+
const WEBVIEW_ID = window.__sparkbunWebviewId;
|
|
18
18
|
const HOST_SOCKET_PORT =
|
|
19
|
-
window.
|
|
19
|
+
window.__sparkbunHostSocketPort ?? window.__sparkbunRpcSocketPort;
|
|
20
20
|
|
|
21
21
|
class Electroview<T extends RPCWithTransport> {
|
|
22
22
|
hostSocket?: WebSocket;
|
|
@@ -43,16 +43,16 @@ class Electroview<T extends RPCWithTransport> {
|
|
|
43
43
|
|
|
44
44
|
// Set up handler for user RPC messages from the host runtime.
|
|
45
45
|
const hostMessageHandler = this.receiveMessageFromHost.bind(this);
|
|
46
|
-
window.
|
|
47
|
-
window.
|
|
46
|
+
window.__sparkbun!.receiveMessageFromHost = hostMessageHandler;
|
|
47
|
+
window.__sparkbun!.receiveMessageFromBun = hostMessageHandler;
|
|
48
48
|
|
|
49
49
|
if (this.rpc) {
|
|
50
50
|
this.rpc.setTransport(this.createTransport());
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const pendingMessages = window.
|
|
53
|
+
const pendingMessages = window.__sparkbunPendingHostMessages;
|
|
54
54
|
if (pendingMessages?.length) {
|
|
55
|
-
window.
|
|
55
|
+
window.__sparkbunPendingHostMessages = [];
|
|
56
56
|
for (const message of pendingMessages) {
|
|
57
57
|
hostMessageHandler(message);
|
|
58
58
|
}
|
|
@@ -83,7 +83,7 @@ class Electroview<T extends RPCWithTransport> {
|
|
|
83
83
|
try {
|
|
84
84
|
const encryptedPacket = JSON.parse(message);
|
|
85
85
|
|
|
86
|
-
const decrypted = await window.
|
|
86
|
+
const decrypted = await window.__sparkbun_decrypt(
|
|
87
87
|
encryptedPacket.encryptedData,
|
|
88
88
|
encryptedPacket.iv,
|
|
89
89
|
encryptedPacket.tag,
|
|
@@ -136,7 +136,7 @@ class Electroview<T extends RPCWithTransport> {
|
|
|
136
136
|
) {
|
|
137
137
|
try {
|
|
138
138
|
const { encryptedData, iv, tag } =
|
|
139
|
-
await window.
|
|
139
|
+
await window.__sparkbun_encrypt(msg);
|
|
140
140
|
|
|
141
141
|
const encryptedPacket = {
|
|
142
142
|
encryptedData: encryptedData,
|
|
@@ -152,11 +152,11 @@ class Electroview<T extends RPCWithTransport> {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
// if socket's are unavailable, fallback to postMessage
|
|
155
|
-
window.
|
|
155
|
+
window.__sparkbunHostBridge?.postMessage(msg);
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
receiveMessageFromHost(msg: unknown) {
|
|
159
|
-
// NOTE: in the webview messages are passed by executing window.
|
|
159
|
+
// NOTE: in the webview messages are passed by executing window.__sparkbun.receiveMessageFromHost(object)
|
|
160
160
|
// so they're already parsed into an object here
|
|
161
161
|
if (this.rpcHandler) {
|
|
162
162
|
this.rpcHandler(msg);
|
|
@@ -182,7 +182,7 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
182
182
|
typeof jsonMessage === "string"
|
|
183
183
|
? jsonMessage
|
|
184
184
|
: JSON.stringify(jsonMessage);
|
|
185
|
-
const wrappedMessage = `window.
|
|
185
|
+
const wrappedMessage = `window.__sparkbun.receiveMessageFromHost(${stringifiedMessage})`;
|
|
186
186
|
this.executeJavascript(wrappedMessage);
|
|
187
187
|
}
|
|
188
188
|
|
|
@@ -191,7 +191,7 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
|
|
|
191
191
|
typeof jsonMessage === "string"
|
|
192
192
|
? jsonMessage
|
|
193
193
|
: JSON.stringify(jsonMessage);
|
|
194
|
-
const wrappedMessage = `window.
|
|
194
|
+
const wrappedMessage = `window.__sparkbun.receiveInternalMessageFromHost(${stringifiedMessage})`;
|
|
195
195
|
this.executeJavascript(wrappedMessage);
|
|
196
196
|
}
|
|
197
197
|
|
package/src/bun/core/Updater.ts
CHANGED
|
@@ -377,10 +377,6 @@ const Updater = {
|
|
|
377
377
|
`from-${currentHash}.tar`,
|
|
378
378
|
);
|
|
379
379
|
|
|
380
|
-
const bunBinDir = dirname(process.execPath);
|
|
381
|
-
const bspatchBinName = currentOS === "win" ? "bspatch.exe" : "bspatch";
|
|
382
|
-
const bspatchPath = join(bunBinDir, bspatchBinName);
|
|
383
|
-
|
|
384
380
|
emitStatus(
|
|
385
381
|
"applying-patch",
|
|
386
382
|
`Applying patch ${patchesApplied + 1} for ${currentHash.slice(0, 8)}...`,
|
|
@@ -390,19 +386,6 @@ const Updater = {
|
|
|
390
386
|
},
|
|
391
387
|
);
|
|
392
388
|
|
|
393
|
-
// Verify all files exist before invoking bspatch
|
|
394
|
-
if (!statSync(bspatchPath, { throwIfNoEntry: false })) {
|
|
395
|
-
emitStatus(
|
|
396
|
-
"patch-failed",
|
|
397
|
-
`bspatch binary not found at ${bspatchPath}`,
|
|
398
|
-
{
|
|
399
|
-
currentHash,
|
|
400
|
-
errorMessage: `bspatch not found: ${bspatchPath}`,
|
|
401
|
-
},
|
|
402
|
-
);
|
|
403
|
-
console.error("bspatch not found:", bspatchPath);
|
|
404
|
-
break;
|
|
405
|
-
}
|
|
406
389
|
if (!statSync(currentTarPath, { throwIfNoEntry: false })) {
|
|
407
390
|
emitStatus("patch-failed", `Old tar not found at ${currentTarPath}`, {
|
|
408
391
|
currentHash,
|
|
@@ -425,44 +408,11 @@ const Updater = {
|
|
|
425
408
|
}
|
|
426
409
|
|
|
427
410
|
try {
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
]);
|
|
434
|
-
|
|
435
|
-
if (patchResult.exitCode !== 0 || patchResult.success === false) {
|
|
436
|
-
const stderr = patchResult.stderr
|
|
437
|
-
? patchResult.stderr.toString()
|
|
438
|
-
: "";
|
|
439
|
-
const stdout = patchResult.stdout
|
|
440
|
-
? patchResult.stdout.toString()
|
|
441
|
-
: "";
|
|
442
|
-
if (updateInfo) {
|
|
443
|
-
updateInfo.error =
|
|
444
|
-
stderr ||
|
|
445
|
-
`bspatch failed with exit code ${patchResult.exitCode}`;
|
|
446
|
-
}
|
|
447
|
-
emitStatus(
|
|
448
|
-
"patch-failed",
|
|
449
|
-
`Patch application failed: ${stderr || `exit code ${patchResult.exitCode}`}`,
|
|
450
|
-
{
|
|
451
|
-
currentHash,
|
|
452
|
-
errorMessage: stderr || `exit code ${patchResult.exitCode}`,
|
|
453
|
-
},
|
|
454
|
-
);
|
|
455
|
-
console.error("bspatch failed", {
|
|
456
|
-
exitCode: patchResult.exitCode,
|
|
457
|
-
stdout,
|
|
458
|
-
stderr,
|
|
459
|
-
bspatchPath,
|
|
460
|
-
oldTar: currentTarPath,
|
|
461
|
-
newTar: tmpPatchedTarFilePath,
|
|
462
|
-
patch: patchFilePath,
|
|
463
|
-
});
|
|
464
|
-
break;
|
|
465
|
-
}
|
|
411
|
+
const { applyPatch } = await import("../../shared/bspatch.ts");
|
|
412
|
+
const oldData = new Uint8Array(await Bun.file(currentTarPath).arrayBuffer());
|
|
413
|
+
const patchBytes = new Uint8Array(await Bun.file(patchFilePath).arrayBuffer());
|
|
414
|
+
const newData = applyPatch(oldData, patchBytes);
|
|
415
|
+
await Bun.write(tmpPatchedTarFilePath, newData);
|
|
466
416
|
} catch (error) {
|
|
467
417
|
emitStatus(
|
|
468
418
|
"patch-failed",
|
|
@@ -472,7 +422,7 @@ const Updater = {
|
|
|
472
422
|
errorMessage: (error as Error).message,
|
|
473
423
|
},
|
|
474
424
|
);
|
|
475
|
-
console.error("bspatch
|
|
425
|
+
console.error("bspatch failed:", error);
|
|
476
426
|
break;
|
|
477
427
|
}
|
|
478
428
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
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(\".sparkbun-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(\".sparkbun-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 SparkBunWebviewTag 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 getInitialNavigationRules() {\n const rawRules = this.getAttribute(\"navigation-rules\");\n if (rawRules === null) {\n return null;\n }\n const trimmed = rawRules.trim();\n if (!trimmed) {\n return [];\n }\n try {\n const parsed = JSON.parse(trimmed);\n if (!Array.isArray(parsed) || !parsed.every((rule) => typeof rule === \"string\")) {\n throw new Error(\"navigation-rules must be a JSON string array\");\n }\n return parsed;\n } catch (error) {\n console.error(\"Invalid navigation-rules attribute:\", error);\n return [];\n }\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 navigationRules = this.getInitialNavigationRules();\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 webviewInitParams = {\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 sandbox,\n transparent,\n passthrough,\n ...navigationRules === null ? {} : { navigationRules }\n };\n const webviewId = await request(\"webviewTagInit\", webviewInitParams);\n this.webviewId = webviewId;\n this.id = `sparkbun-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(\"sparkbun-webview\")) {\n customElements.define(\"sparkbun-webview\", SparkBunWebviewTag);\n }\n const injectStyles = () => {\n const style = document.createElement(\"style\");\n style.textContent = `\nsparkbun-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 SparkBunWgpuTag 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 = `sparkbun-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(\"sparkbun-wgpu\")) {\n customElements.define(\"sparkbun-wgpu\", SparkBunWgpuTag);\n }\n const injectStyles = () => {\n const style = document.createElement(\"style\");\n style.textContent = `\nsparkbun-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};\nvar defaultUserMessageHandler = (msg) => {\n if (!window.__electrobunPendingHostMessages) {\n window.__electrobunPendingHostMessages = [];\n }\n window.__electrobunPendingHostMessages.push(msg);\n};\nif (!window.__electrobun) {\n window.__electrobun = {\n receiveInternalMessageFromHost: internalMessageHandler,\n receiveMessageFromHost: defaultUserMessageHandler,\n receiveInternalMessageFromBun: internalMessageHandler,\n receiveMessageFromBun: defaultUserMessageHandler\n };\n} else {\n window.__electrobun.receiveInternalMessageFromHost = internalMessageHandler;\n window.__electrobun.receiveMessageFromHost = defaultUserMessageHandler;\n window.__electrobun.receiveInternalMessageFromBun = internalMessageHandler;\n window.__electrobun.receiveMessageFromBun = defaultUserMessageHandler;\n}\nwindow.__electrobunSendToHost = (message) => {\n emitWebviewEvent(\"host-message\", JSON.stringify(message));\n};\ninitLifecycleEvents();\ninitCmdClickHandling();\ninitSPANavigationInterception();\ninitOverscrollPrevention();\ninitDragRegions();\ninitWebviewTag();\ninitWgpuTag();\n})();";
|
|
2
|
-
export const preloadScriptSandboxed = "(function(){// src/bun/preload/events.ts\nfunction emitWebviewEvent(eventName, detail) {\n setTimeout(() => {\n const bridge = window.
|
|
1
|
+
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.__sparkbunSecretKeyBytes));\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.__sparkbun_encrypt = encryptString;\n window.__sparkbun_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.__sparkbunInternalBridge?.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.__sparkbunWebviewId\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(\".sparkbun-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(\".sparkbun-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.__sparkbunWindowId });\n }\n });\n document.addEventListener(\"mouseup\", (e) => {\n if (isAppRegionDrag(e)) {\n send(\"stopWindowMove\", { id: window.__sparkbunWindowId });\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 SparkBunWebviewTag 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 getInitialNavigationRules() {\n const rawRules = this.getAttribute(\"navigation-rules\");\n if (rawRules === null) {\n return null;\n }\n const trimmed = rawRules.trim();\n if (!trimmed) {\n return [];\n }\n try {\n const parsed = JSON.parse(trimmed);\n if (!Array.isArray(parsed) || !parsed.every((rule) => typeof rule === \"string\")) {\n throw new Error(\"navigation-rules must be a JSON string array\");\n }\n return parsed;\n } catch (error) {\n console.error(\"Invalid navigation-rules attribute:\", error);\n return [];\n }\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 navigationRules = this.getInitialNavigationRules();\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 webviewInitParams = {\n hostWebviewId: window.__sparkbunWebviewId,\n windowId: window.__sparkbunWindowId,\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 sandbox,\n transparent,\n passthrough,\n ...navigationRules === null ? {} : { navigationRules }\n };\n const webviewId = await request(\"webviewTagInit\", webviewInitParams);\n this.webviewId = webviewId;\n this.id = `sparkbun-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(\"sparkbun-webview\")) {\n customElements.define(\"sparkbun-webview\", SparkBunWebviewTag);\n }\n const injectStyles = () => {\n const style = document.createElement(\"style\");\n style.textContent = `\nsparkbun-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 SparkBunWgpuTag 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.__sparkbunWindowId,\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 = `sparkbun-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(\"sparkbun-wgpu\")) {\n customElements.define(\"sparkbun-wgpu\", SparkBunWgpuTag);\n }\n const injectStyles = () => {\n const style = document.createElement(\"style\");\n style.textContent = `\nsparkbun-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.__sparkbunEventBridge || window.__sparkbunInternalBridge;\n bridge?.postMessage(JSON.stringify({\n id: \"webviewEvent\",\n type: \"message\",\n payload: {\n id: window.__sparkbunWebviewId,\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};\nvar defaultUserMessageHandler = (msg) => {\n if (!window.__sparkbunPendingHostMessages) {\n window.__sparkbunPendingHostMessages = [];\n }\n window.__sparkbunPendingHostMessages.push(msg);\n};\nif (!window.__sparkbun) {\n window.__sparkbun = {\n receiveInternalMessageFromHost: internalMessageHandler,\n receiveMessageFromHost: defaultUserMessageHandler,\n receiveInternalMessageFromBun: internalMessageHandler,\n receiveMessageFromBun: defaultUserMessageHandler\n };\n} else {\n window.__sparkbun.receiveInternalMessageFromHost = internalMessageHandler;\n window.__sparkbun.receiveMessageFromHost = defaultUserMessageHandler;\n window.__sparkbun.receiveInternalMessageFromBun = internalMessageHandler;\n window.__sparkbun.receiveMessageFromBun = defaultUserMessageHandler;\n}\nwindow.__sparkbunSendToHost = (message) => {\n emitWebviewEvent(\"host-message\", JSON.stringify(message));\n};\ninitLifecycleEvents();\ninitCmdClickHandling();\ninitSPANavigationInterception();\ninitOverscrollPrevention();\ninitDragRegions();\ninitWebviewTag();\ninitWgpuTag();\n})();";
|
|
2
|
+
export const preloadScriptSandboxed = "(function(){// src/bun/preload/events.ts\nfunction emitWebviewEvent(eventName, detail) {\n setTimeout(() => {\n const bridge = window.__sparkbunEventBridge || window.__sparkbunInternalBridge;\n bridge?.postMessage(JSON.stringify({\n id: \"webviewEvent\",\n type: \"message\",\n payload: {\n id: window.__sparkbunWebviewId,\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})();";
|
|
@@ -29,13 +29,13 @@ function isAppRegionDrag(e: MouseEvent): boolean {
|
|
|
29
29
|
export function initDragRegions() {
|
|
30
30
|
document.addEventListener("mousedown", (e) => {
|
|
31
31
|
if (isAppRegionDrag(e)) {
|
|
32
|
-
send("startWindowMove", { id: window.
|
|
32
|
+
send("startWindowMove", { id: window.__sparkbunWindowId });
|
|
33
33
|
}
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
document.addEventListener("mouseup", (e) => {
|
|
37
37
|
if (isAppRegionDrag(e)) {
|
|
38
|
-
send("stopWindowMove", { id: window.
|
|
38
|
+
send("stopWindowMove", { id: window.__sparkbunWindowId });
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
41
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Encryption/Decryption for secure RPC
|
|
2
|
-
// Uses per-webview secret key set in window.
|
|
2
|
+
// Uses per-webview secret key set in window.__sparkbunSecretKeyBytes
|
|
3
3
|
|
|
4
4
|
import "./globals.d.ts";
|
|
5
5
|
|
|
@@ -31,7 +31,7 @@ async function generateKeyFromBytes(rawKey: Uint8Array): Promise<CryptoKey> {
|
|
|
31
31
|
|
|
32
32
|
export async function initEncryption(): Promise<void> {
|
|
33
33
|
const secretKey = await generateKeyFromBytes(
|
|
34
|
-
new Uint8Array(window.
|
|
34
|
+
new Uint8Array(window.__sparkbunSecretKeyBytes),
|
|
35
35
|
);
|
|
36
36
|
|
|
37
37
|
const encryptString = async (
|
|
@@ -81,6 +81,6 @@ export async function initEncryption(): Promise<void> {
|
|
|
81
81
|
return decoder.decode(decryptedBuffer);
|
|
82
82
|
};
|
|
83
83
|
|
|
84
|
-
window.
|
|
85
|
-
window.
|
|
84
|
+
window.__sparkbun_encrypt = encryptString;
|
|
85
|
+
window.__sparkbun_decrypt = decryptString;
|
|
86
86
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Shared Event Emission for webview lifecycle events
|
|
2
|
-
// Uses
|
|
3
|
-
// Falls back to
|
|
2
|
+
// Uses __sparkbunEventBridge which is available on ALL webviews (including sandboxed)
|
|
3
|
+
// Falls back to __sparkbunInternalBridge for backwards compatibility until native code
|
|
4
4
|
// is updated to include the eventBridge handler
|
|
5
5
|
// This is a one-way channel for emitting events to native/bun - no RPC capability
|
|
6
6
|
|
|
@@ -13,13 +13,13 @@ export function emitWebviewEvent(eventName: string, detail: string) {
|
|
|
13
13
|
// Prefer eventBridge (available on all webviews), fall back to internalBridge
|
|
14
14
|
// (for backwards compatibility until native code adds eventBridge handler)
|
|
15
15
|
const bridge =
|
|
16
|
-
window.
|
|
16
|
+
window.__sparkbunEventBridge || window.__sparkbunInternalBridge;
|
|
17
17
|
bridge?.postMessage(
|
|
18
18
|
JSON.stringify({
|
|
19
19
|
id: "webviewEvent",
|
|
20
20
|
type: "message",
|
|
21
21
|
payload: {
|
|
22
|
-
id: window.
|
|
22
|
+
id: window.__sparkbunWebviewId,
|
|
23
23
|
eventName,
|
|
24
24
|
detail,
|
|
25
25
|
},
|
|
@@ -3,37 +3,37 @@
|
|
|
3
3
|
|
|
4
4
|
declare global {
|
|
5
5
|
interface Window {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
__sparkbunWebviewId: number;
|
|
7
|
+
__sparkbunWindowId: number;
|
|
8
|
+
__sparkbunRpcSocketPort: number;
|
|
9
|
+
__sparkbunHostSocketPort?: number;
|
|
10
|
+
__sparkbunSecretKeyBytes: number[];
|
|
11
11
|
// Event-only bridge (all webviews, including sandboxed)
|
|
12
|
-
|
|
12
|
+
__sparkbunEventBridge?: {
|
|
13
13
|
postMessage: (message: string) => void;
|
|
14
14
|
};
|
|
15
15
|
// Internal RPC bridge (trusted webviews only)
|
|
16
|
-
|
|
16
|
+
__sparkbunInternalBridge?: {
|
|
17
17
|
postMessage: (message: string) => void;
|
|
18
18
|
};
|
|
19
19
|
// User RPC bridge (trusted webviews only)
|
|
20
|
-
|
|
20
|
+
__sparkbunHostBridge?: {
|
|
21
21
|
postMessage: (message: string) => void;
|
|
22
22
|
};
|
|
23
|
-
|
|
23
|
+
__sparkbunBunBridge?: {
|
|
24
24
|
postMessage: (message: string) => void;
|
|
25
25
|
};
|
|
26
|
-
|
|
26
|
+
__sparkbun_encrypt: (
|
|
27
27
|
plaintext: string,
|
|
28
28
|
) => Promise<{ encryptedData: string; iv: string; tag: string }>;
|
|
29
|
-
|
|
29
|
+
__sparkbun_decrypt: (
|
|
30
30
|
encryptedData: string,
|
|
31
31
|
iv: string,
|
|
32
32
|
tag: string,
|
|
33
33
|
) => Promise<string>;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
__sparkbunSendToHost: (message: unknown) => void;
|
|
35
|
+
__sparkbunPendingHostMessages?: unknown[];
|
|
36
|
+
__sparkbun: {
|
|
37
37
|
receiveMessageFromHost: (msg: unknown) => void;
|
|
38
38
|
receiveInternalMessageFromHost: (msg: unknown) => void;
|
|
39
39
|
receiveMessageFromBun: (msg: unknown) => void;
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
// Only includes: lifecycle events, cmd+click handling, overscroll prevention
|
|
6
6
|
//
|
|
7
7
|
// Before this script runs, the following must be set:
|
|
8
|
-
// - window.
|
|
9
|
-
// - window.
|
|
10
|
-
// - window.
|
|
8
|
+
// - window.__sparkbunWebviewId
|
|
9
|
+
// - window.__sparkbunWindowId
|
|
10
|
+
// - window.__sparkbunEventBridge (event emission only)
|
|
11
11
|
|
|
12
12
|
import "./globals.d.ts";
|
|
13
13
|
import {
|
package/src/bun/preload/index.ts
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
// Includes: RPC, encryption, drag regions, webview tags, lifecycle events
|
|
5
5
|
//
|
|
6
6
|
// Before this script runs, the following must be set:
|
|
7
|
-
// - window.
|
|
8
|
-
// - window.
|
|
9
|
-
// - window.
|
|
10
|
-
// - window.
|
|
11
|
-
// - window.
|
|
12
|
-
// - window.
|
|
13
|
-
// - window.
|
|
14
|
-
// - window.
|
|
15
|
-
// - window.
|
|
7
|
+
// - window.__sparkbunWebviewId
|
|
8
|
+
// - window.__sparkbunWindowId
|
|
9
|
+
// - window.__sparkbunRpcSocketPort
|
|
10
|
+
// - window.__sparkbunHostSocketPort (optional alias)
|
|
11
|
+
// - window.__sparkbunSecretKeyBytes
|
|
12
|
+
// - window.__sparkbunEventBridge (event emission - all webviews)
|
|
13
|
+
// - window.__sparkbunInternalBridge (internal RPC - trusted only)
|
|
14
|
+
// - window.__sparkbunHostBridge (user RPC - trusted only)
|
|
15
|
+
// - window.__sparkbunBunBridge (legacy alias)
|
|
16
16
|
|
|
17
17
|
import "./globals.d.ts";
|
|
18
18
|
import { initEncryption } from "./encryption";
|
|
@@ -42,28 +42,28 @@ const internalMessageHandler = (msg: unknown) => {
|
|
|
42
42
|
const defaultUserMessageHandler = (msg: unknown) => {
|
|
43
43
|
// Buffer user RPC packets that arrive before the page-specific Electroview
|
|
44
44
|
// instance installs the real handler.
|
|
45
|
-
if (!window.
|
|
46
|
-
window.
|
|
45
|
+
if (!window.__sparkbunPendingHostMessages) {
|
|
46
|
+
window.__sparkbunPendingHostMessages = [];
|
|
47
47
|
}
|
|
48
|
-
window.
|
|
48
|
+
window.__sparkbunPendingHostMessages.push(msg);
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
if (!window.
|
|
52
|
-
window.
|
|
51
|
+
if (!window.__sparkbun) {
|
|
52
|
+
window.__sparkbun = {
|
|
53
53
|
receiveInternalMessageFromHost: internalMessageHandler,
|
|
54
54
|
receiveMessageFromHost: defaultUserMessageHandler,
|
|
55
55
|
receiveInternalMessageFromBun: internalMessageHandler,
|
|
56
56
|
receiveMessageFromBun: defaultUserMessageHandler,
|
|
57
57
|
};
|
|
58
58
|
} else {
|
|
59
|
-
window.
|
|
60
|
-
window.
|
|
61
|
-
window.
|
|
62
|
-
window.
|
|
59
|
+
window.__sparkbun.receiveInternalMessageFromHost = internalMessageHandler;
|
|
60
|
+
window.__sparkbun.receiveMessageFromHost = defaultUserMessageHandler;
|
|
61
|
+
window.__sparkbun.receiveInternalMessageFromBun = internalMessageHandler;
|
|
62
|
+
window.__sparkbun.receiveMessageFromBun = defaultUserMessageHandler;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// Allow preload scripts to send custom messages to the host webview
|
|
66
|
-
window.
|
|
66
|
+
window.__sparkbunSendToHost = (message: unknown) => {
|
|
67
67
|
emitWebviewEvent("host-message", JSON.stringify(message));
|
|
68
68
|
};
|
|
69
69
|
|