sparkbun 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist-linux-arm64/libNativeWrapper.so +0 -0
  2. package/dist-linux-arm64/{libElectrobunCore.so → libSparkBunCore.so} +0 -0
  3. package/dist-linux-x64/libNativeWrapper.so +0 -0
  4. package/dist-linux-x64/{libElectrobunCore.so → libSparkBunCore.so} +0 -0
  5. package/dist-macos-arm64/libNativeWrapper.dylib +0 -0
  6. package/dist-macos-arm64/libSparkBunCore.dylib +0 -0
  7. package/dist-macos-x64/libNativeWrapper.dylib +0 -0
  8. package/dist-macos-x64/libSparkBunCore.dylib +0 -0
  9. package/dist-macos-x64/libasar.dylib +0 -0
  10. package/dist-macos-x64/libwebgpu_dawn.dylib +0 -0
  11. package/dist-macos-x64/process_helper +0 -0
  12. package/dist-win-arm64/SparkBunCore.dll +0 -0
  13. package/dist-win-arm64/WebView2Loader.dll +0 -0
  14. package/dist-win-arm64/libNativeWrapper.dll +0 -0
  15. package/dist-win-arm64/zig-asar/arm64/libasar.dll +0 -0
  16. package/dist-win-arm64/zig-asar/x64/libasar.dll +0 -0
  17. package/dist-win-x64/SparkBunCore.dll +0 -0
  18. package/package.json +1 -1
  19. package/scripts/setup-win-arm64.ps1 +19 -0
  20. package/src/browser/global.d.ts +11 -11
  21. package/src/browser/index.ts +10 -10
  22. package/src/bun/core/BrowserView.ts +2 -2
  23. package/src/bun/core/Updater.ts +6 -56
  24. package/src/bun/preload/.generated/compiled.ts +2 -2
  25. package/src/bun/preload/dragRegions.ts +2 -2
  26. package/src/bun/preload/encryption.ts +4 -4
  27. package/src/bun/preload/events.ts +4 -4
  28. package/src/bun/preload/globals.d.ts +14 -14
  29. package/src/bun/preload/index-sandboxed.ts +3 -3
  30. package/src/bun/preload/index.ts +19 -19
  31. package/src/bun/preload/internalRpc.ts +3 -3
  32. package/src/bun/preload/webviewTag.ts +2 -2
  33. package/src/bun/preload/wgpuTag.ts +1 -1
  34. package/src/bun/proc/native.ts +6 -6
  35. package/src/cli/index.ts +18 -53
  36. package/src/core/build.zig +1 -1
  37. package/src/core/main.zig +25 -24
  38. package/src/installer/installer-template.ts +1 -1
  39. package/src/launcher/main.ts +10 -10
  40. package/src/native/linux/cef_process_helper_linux.cpp +4 -4
  41. package/src/native/linux/nativeWrapper.cpp +230 -230
  42. package/src/native/macos/cef_process_helper_mac.cc +4 -4
  43. package/src/native/macos/nativeWrapper.mm +100 -100
  44. package/src/native/shared/accelerator_parser.h +5 -5
  45. package/src/native/shared/app_paths.h +7 -7
  46. package/src/native/shared/asar.h +5 -5
  47. package/src/native/shared/cache_migration.h +14 -14
  48. package/src/native/shared/callbacks.h +5 -5
  49. package/src/native/shared/cef_response_filter.h +18 -18
  50. package/src/native/shared/chromium_flags.h +6 -6
  51. package/src/native/shared/config.h +5 -5
  52. package/src/native/shared/download_event.h +5 -5
  53. package/src/native/shared/ffi_helpers.h +6 -6
  54. package/src/native/shared/glob_match.h +5 -5
  55. package/src/native/shared/json_menu_parser.h +5 -5
  56. package/src/native/shared/mime_types.h +5 -5
  57. package/src/native/shared/navigation_rules.h +5 -5
  58. package/src/native/shared/partition_context.h +5 -5
  59. package/src/native/shared/permissions.h +5 -5
  60. package/src/native/shared/permissions_cef.h +5 -5
  61. package/src/native/shared/preload_script.h +5 -5
  62. package/src/native/shared/shutdown_guard.h +5 -5
  63. package/src/native/shared/thread_safe_map.h +5 -5
  64. package/src/native/shared/webview_storage.h +5 -5
  65. package/src/native/win/cef_process_helper_win.cpp +4 -4
  66. package/src/native/win/dcomp_compositor.h +1 -1
  67. package/src/native/win/nativeWrapper.cpp +288 -288
  68. package/src/shared/bsdiff.ts +236 -0
  69. package/src/shared/bspatch.ts +98 -0
  70. package/dist-linux-arm64/bsdiff +0 -0
  71. package/dist-linux-arm64/bspatch +0 -0
  72. package/dist-linux-x64/bsdiff +0 -0
  73. package/dist-linux-x64/bspatch +0 -0
  74. package/dist-macos-arm64/bsdiff +0 -0
  75. package/dist-macos-arm64/bspatch +0 -0
  76. package/dist-macos-arm64/libElectrobunCore.dylib +0 -0
  77. package/dist-win-x64/ElectrobunCore.dll +0 -0
  78. package/dist-win-x64/bsdiff.exe +0 -0
  79. package/dist-win-x64/bspatch.exe +0 -0
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sparkbun",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Build fast, lightweight, cross-platform desktop apps with TypeScript and Bun.",
5
5
  "license": "MIT",
6
6
  "author": "SparkBun Contributors",
@@ -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"
@@ -19,17 +19,17 @@ interface MessageHandler {
19
19
 
20
20
  declare global {
21
21
  interface Window {
22
- __electrobunWebviewId: number;
23
- __electrobunWindowId: number;
24
- __electrobunRpcSocketPort: number;
25
- __electrobunHostSocketPort?: number;
26
- __electrobun?: SparkBunBridge;
27
- __electrobunPendingHostMessages?: unknown[];
28
- __electrobun_encrypt: (msg: string) => Promise<SparkBunEncryptResult>;
29
- __electrobun_decrypt: (encryptedData: string, iv: string, tag: string) => Promise<string>;
30
- __electrobunInternalBridge?: MessageHandler;
31
- __electrobunHostBridge?: MessageHandler;
32
- __electrobunBunBridge?: MessageHandler;
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
 
@@ -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.__electrobunWebviewId;
17
+ const WEBVIEW_ID = window.__sparkbunWebviewId;
18
18
  const HOST_SOCKET_PORT =
19
- window.__electrobunHostSocketPort ?? window.__electrobunRpcSocketPort;
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.__electrobun!.receiveMessageFromHost = hostMessageHandler;
47
- window.__electrobun!.receiveMessageFromBun = hostMessageHandler;
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.__electrobunPendingHostMessages;
53
+ const pendingMessages = window.__sparkbunPendingHostMessages;
54
54
  if (pendingMessages?.length) {
55
- window.__electrobunPendingHostMessages = [];
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.__electrobun_decrypt(
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.__electrobun_encrypt(msg);
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.__electrobunHostBridge?.postMessage(msg);
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.__electrobun.receiveMessageFromHost(object)
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.__electrobun.receiveMessageFromHost(${stringifiedMessage})`;
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.__electrobun.receiveInternalMessageFromHost(${stringifiedMessage})`;
194
+ const wrappedMessage = `window.__sparkbun.receiveInternalMessageFromHost(${stringifiedMessage})`;
195
195
  this.executeJavascript(wrappedMessage);
196
196
  }
197
197
 
@@ -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 patchResult = Bun.spawnSync([
429
- bspatchPath,
430
- currentTarPath,
431
- tmpPatchedTarFilePath,
432
- patchFilePath,
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 threw", error, { bspatchPath });
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.__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})();";
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.__electrobunWindowId });
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.__electrobunWindowId });
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.__electrobunSecretKeyBytes
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.__electrobunSecretKeyBytes),
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.__electrobun_encrypt = encryptString;
85
- window.__electrobun_decrypt = decryptString;
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 __electrobunEventBridge which is available on ALL webviews (including sandboxed)
3
- // Falls back to __electrobunInternalBridge for backwards compatibility until native code
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.__electrobunEventBridge || window.__electrobunInternalBridge;
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.__electrobunWebviewId,
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
- __electrobunWebviewId: number;
7
- __electrobunWindowId: number;
8
- __electrobunRpcSocketPort: number;
9
- __electrobunHostSocketPort?: number;
10
- __electrobunSecretKeyBytes: number[];
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
- __electrobunEventBridge?: {
12
+ __sparkbunEventBridge?: {
13
13
  postMessage: (message: string) => void;
14
14
  };
15
15
  // Internal RPC bridge (trusted webviews only)
16
- __electrobunInternalBridge?: {
16
+ __sparkbunInternalBridge?: {
17
17
  postMessage: (message: string) => void;
18
18
  };
19
19
  // User RPC bridge (trusted webviews only)
20
- __electrobunHostBridge?: {
20
+ __sparkbunHostBridge?: {
21
21
  postMessage: (message: string) => void;
22
22
  };
23
- __electrobunBunBridge?: {
23
+ __sparkbunBunBridge?: {
24
24
  postMessage: (message: string) => void;
25
25
  };
26
- __electrobun_encrypt: (
26
+ __sparkbun_encrypt: (
27
27
  plaintext: string,
28
28
  ) => Promise<{ encryptedData: string; iv: string; tag: string }>;
29
- __electrobun_decrypt: (
29
+ __sparkbun_decrypt: (
30
30
  encryptedData: string,
31
31
  iv: string,
32
32
  tag: string,
33
33
  ) => Promise<string>;
34
- __electrobunSendToHost: (message: unknown) => void;
35
- __electrobunPendingHostMessages?: unknown[];
36
- __electrobun: {
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.__electrobunWebviewId
9
- // - window.__electrobunWindowId
10
- // - window.__electrobunEventBridge (event emission only)
8
+ // - window.__sparkbunWebviewId
9
+ // - window.__sparkbunWindowId
10
+ // - window.__sparkbunEventBridge (event emission only)
11
11
 
12
12
  import "./globals.d.ts";
13
13
  import {
@@ -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.__electrobunWebviewId
8
- // - window.__electrobunWindowId
9
- // - window.__electrobunRpcSocketPort
10
- // - window.__electrobunHostSocketPort (optional alias)
11
- // - window.__electrobunSecretKeyBytes
12
- // - window.__electrobunEventBridge (event emission - all webviews)
13
- // - window.__electrobunInternalBridge (internal RPC - trusted only)
14
- // - window.__electrobunHostBridge (user RPC - trusted only)
15
- // - window.__electrobunBunBridge (legacy alias)
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.__electrobunPendingHostMessages) {
46
- window.__electrobunPendingHostMessages = [];
45
+ if (!window.__sparkbunPendingHostMessages) {
46
+ window.__sparkbunPendingHostMessages = [];
47
47
  }
48
- window.__electrobunPendingHostMessages.push(msg);
48
+ window.__sparkbunPendingHostMessages.push(msg);
49
49
  };
50
50
 
51
- if (!window.__electrobun) {
52
- window.__electrobun = {
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.__electrobun.receiveInternalMessageFromHost = internalMessageHandler;
60
- window.__electrobun.receiveMessageFromHost = defaultUserMessageHandler;
61
- window.__electrobun.receiveInternalMessageFromBun = internalMessageHandler;
62
- window.__electrobun.receiveMessageFromBun = defaultUserMessageHandler;
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.__electrobunSendToHost = (message: unknown) => {
66
+ window.__sparkbunSendToHost = (message: unknown) => {
67
67
  emitWebviewEvent("host-message", JSON.stringify(message));
68
68
  };
69
69