electrobun 1.18.4-beta.10 → 1.18.4-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  <div align="center">
8
8
  Get started with a template <br />
9
- <code><strong>npx electrobun init</strong></code>
9
+ <code><strong>hutch electrobun init</strong></code>
10
10
  </div>
11
11
 
12
12
 
@@ -109,22 +109,22 @@ Ways to get involved:
109
109
  - Let me know what you're building with Electrobun
110
110
 
111
111
  ## Development Setup
112
- Building apps with Electrobun is as easy as updating your package.json dependencies with `npm add electrobun` or try one of our templates via `npx electrobun init`.
112
+ Building apps with Electrobun is as easy as installing Hutch and running `hutch electrobun init`.
113
113
 
114
114
  **This section is for building Electrobun from source locally in order to contribute fixes to it.**
115
115
 
116
116
  ### Prerequisites
117
117
 
118
- Install Dash globally before building Electrobun:
118
+ Install Hutch globally before building Electrobun:
119
119
 
120
120
  ```bash
121
- curl -fsSL https://electrobun-artifacts.blackboard.sh/dash-cli/install.sh | sh
121
+ curl -fsSL https://hutch.blackboard.sh/hutch/install.sh | sh
122
122
  ```
123
123
 
124
124
  On Windows PowerShell:
125
125
 
126
126
  ```powershell
127
- & ([scriptblock]::Create((irm https://electrobun-artifacts.blackboard.sh/dash-cli/install.ps1)))
127
+ & ([scriptblock]::Create((irm https://hutch.blackboard.sh/hutch/install.ps1)))
128
128
  ```
129
129
 
130
130
  **macOS:**
@@ -147,8 +147,8 @@ On Ubuntu/Debian based distros: `sudo apt install build-essential cmake pkg-conf
147
147
  ```bash
148
148
  git clone --recurse-submodules https://github.com/blackboardsh/electrobun.git
149
149
  cd electrobun/package
150
- dash install
151
- dash dev:clean
150
+ hutch install
151
+ hutch dev:clean
152
152
  ```
153
153
 
154
154
  ### Development Workflow
@@ -158,10 +158,10 @@ dash dev:clean
158
158
  cd electrobun/package
159
159
 
160
160
  # After making changes to source code
161
- dash dev
161
+ hutch dev
162
162
 
163
163
  # If you need a completely fresh start
164
- dash dev:clean
164
+ hutch dev:clean
165
165
  ```
166
166
 
167
167
  With sibling `jsc`, `cottontail`, `dash-cloud`, and `electrobun` checkouts, use
@@ -169,20 +169,20 @@ one command to build only changed native layers and run Kitchen with the local
169
169
  stack:
170
170
 
171
171
  ```bash
172
- dash dev --local
172
+ hutch dev --local
173
173
  ```
174
174
 
175
- The first Dash is globally installed. Stack preparation explicitly selects the
176
- completed local Dash engine and Cottontail build for the remainder of the
175
+ The first Hutch is globally installed. Stack preparation explicitly selects the
176
+ completed local Hutch engine and Cottontail build for the remainder of the
177
177
  command.
178
178
 
179
179
  ### Additional Commands
180
180
 
181
181
  All commands are run from the `/package` directory:
182
182
 
183
- - `dash dev:canary` - Build and run kitchen sink in canary mode
184
- - `dash build:dev` - Build Electrobun in development mode
185
- - `dash build:release` - Build Electrobun in release mode
183
+ - `hutch dev:canary` - Build and run kitchen sink in canary mode
184
+ - `hutch build:dev` - Build Electrobun in development mode
185
+ - `hutch build:release` - Build Electrobun in release mode
186
186
 
187
187
  ### Debugging
188
188
 
package/dash.config.ts CHANGED
@@ -1,28 +1,28 @@
1
- // @dash cli=0.2.0-canary.2 cottontail=0.1.1-canary.2
1
+ // @dash cli=0.3.1 cottontail=0.2.3
2
2
  export default {
3
3
  scripts: {
4
- start: "dash src/sdks/bun/index.ts",
4
+ start: "hutch src/sdks/bun/index.ts",
5
5
  "check-zig-version": "vendors/zig/zig version",
6
6
  "check-rust-version": "vendors/rust/bin/rustc --version",
7
- "build:dev": "dash build.ts",
7
+ "build:dev": "hutch build.ts",
8
8
  "build:local": "scripts/build-local.js",
9
- "build:release": "dash build.ts --release",
9
+ "build:release": "hutch build.ts --release",
10
10
  dev: "scripts/dev.ts",
11
11
  "dev:test": "scripts/dev.test.ts",
12
- "dev:clean": "cd ../kitchen && rm -rf node_modules vendors/cef && cd ../package && dash dev",
13
- "dev:canary": "dash install && dash build:release && cd ../kitchen && dash install && dash electrobun build --env=canary",
14
- "dev:production": "dash install && dash build:release && cd ../kitchen && dash install && dash electrobun build --env=production",
15
- "build:docs:release": "cd ../docs && dash run build",
16
- "npm:publish": "dash build:release && npm publish",
17
- "npm:publish:beta": "dash build:release && npm publish --tag beta",
12
+ "dev:clean": "cd ../kitchen && rm -rf node_modules vendors/cef && cd ../package && hutch dev",
13
+ "dev:canary": "hutch install && hutch build:release && cd ../kitchen && hutch install && hutch electrobun build --env=canary",
14
+ "dev:production": "hutch install && hutch build:release && cd ../kitchen && hutch install && hutch electrobun build --env=production",
15
+ "build:docs:release": "cd ../docs && hutch run build",
16
+ "npm:publish": "hutch build:release && npm publish",
17
+ "npm:publish:beta": "hutch build:release && npm publish --tag beta",
18
18
  typecheck: "./node_modules/.bin/tsc --noEmit && cd ../kitchen && ./node_modules/.bin/tsc --noEmit",
19
- "push:beta": "dash typecheck && node scripts/push-version.js beta",
20
- "push:patch": "dash typecheck && node scripts/push-version.js patch",
21
- "push:minor": "dash typecheck && node scripts/push-version.js minor",
22
- "push:major": "dash typecheck && node scripts/push-version.js major",
23
- "push:stable": "dash typecheck && node scripts/push-version.js stable",
19
+ "push:beta": "hutch typecheck && node scripts/push-version.js beta",
20
+ "push:patch": "hutch typecheck && node scripts/push-version.js patch",
21
+ "push:minor": "hutch typecheck && node scripts/push-version.js minor",
22
+ "push:major": "hutch typecheck && node scripts/push-version.js major",
23
+ "push:stable": "hutch typecheck && node scripts/push-version.js stable",
24
24
  "build:push:artifacts": "node scripts/build-and-upload-artifacts.js",
25
- "test:unit": "dash test src/shared src/sdks/bun src/config",
26
- "bump-cef": "dash scripts/update-cef-version.ts",
25
+ "test:unit": "hutch test src/shared src/sdks/bun src/config",
26
+ "bump-cef": "hutch scripts/update-cef-version.ts",
27
27
  },
28
28
  };
@@ -1,5 +1,5 @@
1
1
  // Auto-generated file. Do not edit directly.
2
- // Run "dash build.ts" or "dash run build:dev" from the package folder to regenerate.
2
+ // Run "hutch build.ts" or "hutch run build:dev" from the package folder to regenerate.
3
3
 
4
4
  // Full preload for trusted webviews (RPC, encryption, drag regions, webview tags)
5
5
  export const preloadScript = "(function(){// src/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}\nfunction toArrayBuffer(bytes) {\n const buffer = new ArrayBuffer(bytes.byteLength);\n new Uint8Array(buffer).set(bytes);\n return buffer;\n}\nasync function generateKeyFromBytes(rawKey) {\n return await window.crypto.subtle.importKey(\"raw\", toArrayBuffer(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: toArrayBuffer(iv) }, secretKey, toArrayBuffer(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: toArrayBuffer(iv) }, secretKey, toArrayBuffer(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/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/preload/dragRegions.ts\nfunction isAppRegionDrag(e) {\n const target = e.target;\n if (!target || !target.closest)\n return false;\n if (target.closest(\".electrobun-webkit-app-region-no-drag\") || target.closest('[style*=\"app-region\"][style*=\"no-drag\"]')) {\n return false;\n }\n const draggableByStyle = target.closest('[style*=\"app-region\"][style*=\"drag\"]');\n const draggableByClass = target.closest(\".electrobun-webkit-app-region-drag\");\n return !!(draggableByStyle || draggableByClass);\n}\nfunction initDragRegions() {\n document.addEventListener(\"mousedown\", (e) => {\n if (isAppRegionDrag(e)) {\n send(\"startWindowMove\", { id: window.__electrobunWindowId });\n }\n });\n document.addEventListener(\"mouseup\", (e) => {\n if (isAppRegionDrag(e)) {\n send(\"stopWindowMove\", { id: window.__electrobunWindowId });\n }\n });\n}\n\n// src/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/preload/webviewTag.ts\nvar webviewRegistry = {};\n\nclass ElectrobunWebviewTag extends HTMLElement {\n webviewId = null;\n maskSelectors = new Set;\n _sync = null;\n transparent = false;\n passthroughEnabled = false;\n hidden = false;\n sandboxed = false;\n _eventListeners = {};\n static get observedAttributes() {\n return [\"src\", \"html\"];\n }\n constructor() {\n super();\n }\n connectedCallback() {\n requestAnimationFrame(() => this.initWebview());\n }\n attributeChangedCallback(name, oldValue, newValue) {\n if (oldValue === newValue)\n return;\n if (newValue === null)\n return;\n if (this.webviewId === null)\n return;\n if (name === \"src\")\n this.loadURL(newValue);\n else if (name === \"html\")\n this.loadHTML(newValue);\n }\n disconnectedCallback() {\n if (this.webviewId !== null) {\n send(\"webviewTagRemove\", { id: this.webviewId });\n delete webviewRegistry[this.webviewId];\n }\n if (this._sync)\n this._sync.stop();\n }\n 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 = `electrobun-webview-${webviewId}`;\n webviewRegistry[webviewId] = this;\n this.setupObservers(initialRect);\n this.syncDimensions(true);\n requestAnimationFrame(() => {\n Object.values(webviewRegistry).forEach((webview) => {\n if (webview !== this && webview.webviewId !== null) {\n webview.syncDimensions(true);\n }\n });\n });\n } catch (err) {\n console.error(\"Failed to init webview:\", err);\n }\n }\n setupObservers(initialRect) {\n const getMasks = () => {\n const rect = this.getBoundingClientRect();\n const masks = [];\n this.maskSelectors.forEach((selector) => {\n try {\n document.querySelectorAll(selector).forEach((el) => {\n const mr = el.getBoundingClientRect();\n masks.push({\n x: mr.x - rect.x,\n y: mr.y - rect.y,\n width: mr.width,\n height: mr.height\n });\n });\n } catch (_e) {}\n });\n return masks;\n };\n this._sync = new OverlaySyncController(this, {\n onSync: (rect, masksJson) => {\n if (this.webviewId === null)\n return;\n send(\"webviewTagResize\", {\n id: this.webviewId,\n frame: rect,\n masks: masksJson\n });\n },\n getMasks,\n burstIntervalMs: 10,\n baseIntervalMs: 100,\n burstDurationMs: 50\n });\n this._sync.setLastRect(initialRect);\n this._sync.start();\n }\n syncDimensions(force = false) {\n if (!this._sync)\n return;\n if (force) {\n this._sync.forceSync();\n }\n }\n loadURL(url) {\n if (this.webviewId === null)\n return;\n this.setAttribute(\"src\", url);\n send(\"webviewTagUpdateSrc\", { id: this.webviewId, url });\n }\n loadHTML(html) {\n if (this.webviewId === null)\n return;\n send(\"webviewTagUpdateHtml\", { id: this.webviewId, html });\n }\n reload() {\n if (this.webviewId !== null)\n send(\"webviewTagReload\", { id: this.webviewId });\n }\n goBack() {\n if (this.webviewId !== null)\n send(\"webviewTagGoBack\", { id: this.webviewId });\n }\n goForward() {\n if (this.webviewId !== null)\n send(\"webviewTagGoForward\", { id: this.webviewId });\n }\n async canGoBack() {\n if (this.webviewId === null)\n return false;\n return await request(\"webviewTagCanGoBack\", {\n id: this.webviewId\n });\n }\n async canGoForward() {\n if (this.webviewId === null)\n return false;\n return await request(\"webviewTagCanGoForward\", {\n id: this.webviewId\n });\n }\n toggleTransparent(value) {\n if (this.webviewId === null)\n return;\n this.transparent = value !== undefined ? value : !this.transparent;\n this.style.opacity = this.transparent ? \"0\" : \"\";\n send(\"webviewTagSetTransparent\", {\n id: this.webviewId,\n transparent: this.transparent\n });\n }\n togglePassthrough(value) {\n if (this.webviewId === null)\n return;\n this.passthroughEnabled = value !== undefined ? value : !this.passthroughEnabled;\n this.style.pointerEvents = this.passthroughEnabled ? \"none\" : \"\";\n send(\"webviewTagSetPassthrough\", {\n id: this.webviewId,\n enablePassthrough: this.passthroughEnabled\n });\n }\n toggleHidden(value) {\n if (this.webviewId === null)\n return;\n this.hidden = value !== undefined ? value : !this.hidden;\n send(\"webviewTagSetHidden\", { id: this.webviewId, hidden: this.hidden });\n }\n addMaskSelector(selector) {\n this.maskSelectors.add(selector);\n this.syncDimensions(true);\n }\n removeMaskSelector(selector) {\n this.maskSelectors.delete(selector);\n this.syncDimensions(true);\n }\n setNavigationRules(rules) {\n if (this.webviewId !== null) {\n send(\"webviewTagSetNavigationRules\", { id: this.webviewId, rules });\n }\n }\n findInPage(searchText, options) {\n if (this.webviewId === null)\n return;\n const forward = options?.forward !== false;\n const matchCase = options?.matchCase || false;\n send(\"webviewTagFindInPage\", {\n id: this.webviewId,\n searchText,\n forward,\n matchCase\n });\n }\n stopFindInPage() {\n if (this.webviewId !== null)\n send(\"webviewTagStopFind\", { id: this.webviewId });\n }\n openDevTools() {\n if (this.webviewId !== null)\n send(\"webviewTagOpenDevTools\", { id: this.webviewId });\n }\n closeDevTools() {\n if (this.webviewId !== null)\n send(\"webviewTagCloseDevTools\", { id: this.webviewId });\n }\n toggleDevTools() {\n if (this.webviewId !== null)\n send(\"webviewTagToggleDevTools\", { id: this.webviewId });\n }\n executeJavascript(js) {\n if (this.webviewId === null)\n return;\n send(\"webviewTagExecuteJavascript\", { id: this.webviewId, js });\n }\n on(event, listener) {\n if (!this._eventListeners[event])\n this._eventListeners[event] = [];\n this._eventListeners[event].push(listener);\n }\n off(event, listener) {\n if (!this._eventListeners[event])\n return;\n const idx = this._eventListeners[event].indexOf(listener);\n if (idx !== -1)\n this._eventListeners[event].splice(idx, 1);\n }\n emit(event, detail) {\n const listeners = this._eventListeners[event];\n if (listeners) {\n const customEvent = new CustomEvent(event, { detail });\n listeners.forEach((fn) => fn(customEvent));\n }\n }\n get src() {\n return this.getAttribute(\"src\");\n }\n set src(value) {\n if (value) {\n this.setAttribute(\"src\", value);\n } else {\n this.removeAttribute(\"src\");\n }\n }\n get html() {\n return this.getAttribute(\"html\");\n }\n set html(value) {\n if (value) {\n this.setAttribute(\"html\", value);\n } else {\n this.removeAttribute(\"html\");\n }\n }\n get preload() {\n return this.getAttribute(\"preload\");\n }\n set preload(value) {\n if (value)\n this.setAttribute(\"preload\", value);\n else\n this.removeAttribute(\"preload\");\n }\n get renderer() {\n return this.getAttribute(\"renderer\") || \"native\";\n }\n set renderer(value) {\n this.setAttribute(\"renderer\", value);\n }\n get sandbox() {\n return this.sandboxed;\n }\n}\nfunction initWebviewTag() {\n if (!customElements.get(\"electrobun-webview\")) {\n customElements.define(\"electrobun-webview\", ElectrobunWebviewTag);\n }\n const injectStyles = () => {\n const style = document.createElement(\"style\");\n style.textContent = `\nelectrobun-webview {\n\tdisplay: block;\n\twidth: 800px;\n\theight: 300px;\n\tbackground: #fff;\n\tbackground-repeat: no-repeat !important;\n\toverflow: hidden;\n}\n`;\n if (document.head?.firstChild) {\n document.head.insertBefore(style, document.head.firstChild);\n } else if (document.head) {\n document.head.appendChild(style);\n }\n };\n if (document.head) {\n injectStyles();\n } else {\n document.addEventListener(\"DOMContentLoaded\", injectStyles);\n }\n}\n\n// src/preload/wgpuTag.ts\nvar wgpuTagRegistry = {};\n\nclass ElectrobunWgpuTag extends HTMLElement {\n wgpuViewId = null;\n maskSelectors = new Set;\n _sync = null;\n transparent = false;\n passthroughEnabled = false;\n hidden = false;\n _ready = false;\n _initializing = false;\n _eventListeners = {};\n constructor() {\n super();\n }\n connectedCallback() {\n requestAnimationFrame(() => {\n if (this.isConnected)\n this.initWgpuView();\n });\n }\n disconnectedCallback() {\n if (this.wgpuViewId !== null) {\n send(\"wgpuTagRemove\", { id: this.wgpuViewId });\n delete wgpuTagRegistry[this.wgpuViewId];\n this.wgpuViewId = null;\n }\n if (this._sync)\n this._sync.stop();\n this._sync = null;\n this._ready = false;\n }\n async initWgpuView() {\n if (this._initializing || this.wgpuViewId !== null)\n return;\n this._initializing = true;\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 if (!this.isConnected) {\n send(\"wgpuTagRemove\", { id: wgpuViewId });\n return;\n }\n this.wgpuViewId = wgpuViewId;\n if (!this.id) {\n this.id = `electrobun-wgpu-${wgpuViewId}`;\n }\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._ready = true;\n this.emit(\"ready\", { id: wgpuViewId });\n } catch (err) {\n console.error(\"Failed to init WGPU view:\", err);\n } finally {\n this._initializing = false;\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 if (event === \"ready\" && this._ready && this.wgpuViewId !== null) {\n const readyEvent = new CustomEvent(event, {\n detail: { id: this.wgpuViewId }\n });\n queueMicrotask(() => {\n if (this._eventListeners[event]?.includes(listener)) {\n listener(readyEvent);\n }\n });\n }\n }\n off(event, listener) {\n if (!this._eventListeners[event])\n return;\n const idx = this._eventListeners[event].indexOf(listener);\n if (idx !== -1)\n this._eventListeners[event].splice(idx, 1);\n }\n emit(event, detail) {\n const listeners = this._eventListeners[event];\n if (listeners) {\n const customEvent = new CustomEvent(event, { detail });\n listeners.forEach((fn) => fn(customEvent));\n }\n }\n}\nfunction initWgpuTag() {\n if (!customElements.get(\"electrobun-wgpu\")) {\n customElements.define(\"electrobun-wgpu\", ElectrobunWgpuTag);\n }\n const injectStyles = () => {\n const style = document.createElement(\"style\");\n style.textContent = `\nelectrobun-wgpu {\n\tdisplay: block;\n\twidth: 800px;\n\theight: 300px;\n\tbackground: #000;\n\toverflow: hidden;\n}\n`;\n if (document.head?.firstChild) {\n document.head.insertBefore(style, document.head.firstChild);\n } else if (document.head) {\n document.head.appendChild(style);\n }\n };\n if (document.head) {\n injectStyles();\n } else {\n document.addEventListener(\"DOMContentLoaded\", injectStyles);\n }\n}\n\n// src/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/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})();";
@@ -1,6 +1,6 @@
1
1
  // Standalone build script for the preload.
2
- // Normally this is run as part of "dash build.ts", but you can run it directly:
3
- // dash src/preload/build.ts
2
+ // Normally this is run as part of "hutch build.ts", but you can run it directly:
3
+ // hutch src/preload/build.ts
4
4
 
5
5
  import { join, dirname } from "path";
6
6
  import { writeFileSync, mkdirSync } from "fs";
@@ -46,7 +46,7 @@ async function buildPreload() {
46
46
  const sandboxedPreloadJs = `(function(){${await sandboxedResult.outputs[0]!.text()}})();`;
47
47
 
48
48
  const outputContent = `// Auto-generated file. Do not edit directly.
49
- // Run "dash build.ts" from the package folder to regenerate.
49
+ // Run "hutch build.ts" from the package folder to regenerate.
50
50
 
51
51
  // Full preload for trusted webviews (RPC, encryption, drag regions, webview tags)
52
52
  export const preloadScript = ${JSON.stringify(fullPreloadJs)};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrobun",
3
- "version": "1.18.4-beta.10",
3
+ "version": "1.18.4-beta.12",
4
4
  "description": "Build ultra fast, tiny, and cross-platform desktop apps with Typescript.",
5
5
  "license": "MIT",
6
6
  "author": "Blackboard Technologies Inc.",