@github/copilot-linux-arm64
Advanced tools
Sorry, the diff of this file is not supported yet
| { | ||
| "name": "@webviewjs/webview-linux-arm64-gnu", | ||
| "version": "0.4.0", | ||
| "cpu": [ | ||
| "arm64" | ||
| ], | ||
| "main": "webview.linux-arm64-gnu.node", | ||
| "files": [ | ||
| "webview.linux-arm64-gnu.node" | ||
| ], | ||
| "description": "Robust cross-platform webview library for Node.js written in Rust. It also works with deno and bun.", | ||
| "keywords": [ | ||
| "webview", | ||
| "node", | ||
| "rust", | ||
| "cross-platform", | ||
| "gui" | ||
| ], | ||
| "license": "MIT", | ||
| "engines": { | ||
| "node": ">= 24" | ||
| }, | ||
| "repository": "git@github.com:webviewjs/webview.git", | ||
| "publishConfig": { | ||
| "registry": "https://registry.npmjs.org/", | ||
| "access": "public" | ||
| }, | ||
| "os": [ | ||
| "linux" | ||
| ], | ||
| "libc": [ | ||
| "glibc" | ||
| ] | ||
| } |
| # `@webviewjs/webview-linux-arm64-gnu` | ||
| This is the **aarch64-unknown-linux-gnu** binary for `@webviewjs/webview` |
Sorry, the diff of this file is not supported yet
| import { execSync } from 'node:child_process'; | ||
| import { copyFileSync, constants, writeFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs'; | ||
| import { dirname, join } from 'node:path'; | ||
| import { execPath } from 'node:process'; | ||
| const isWindows = process.platform === 'win32'; | ||
| const isMac = process.platform === 'darwin'; | ||
| const NODE_SEA_FUSE = 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2'; | ||
| function writeSeaConfig(main, dest, resources = {}) { | ||
| const config = { | ||
| main, | ||
| output: join(dirname(dest), 'sea-prep.blob'), | ||
| disableExperimentalSEAWarning: true, | ||
| assets: resources, | ||
| }; | ||
| writeFileSync(dest, JSON.stringify(config, null, 2)); | ||
| } | ||
| function run(command, args) { | ||
| const cmd = !args?.length ? command : `${command} ${args.join(' ')}`; | ||
| execSync(cmd, { stdio: 'inherit' }); | ||
| } | ||
| function generateBlob(configPath) { | ||
| run('node', ['--experimental-sea-config', configPath]); | ||
| return join(dirname(configPath), 'sea-prep.blob'); | ||
| } | ||
| function copyNode(output, name) { | ||
| const ext = isWindows ? '.exe' : ''; | ||
| const f = join(output, name + ext); | ||
| copyFileSync(execPath, f, constants.COPYFILE_FICLONE); | ||
| return f; | ||
| } | ||
| function removeSignature(path) { | ||
| if (!isWindows && !isMac) return; | ||
| if (isWindows) { | ||
| try { | ||
| run('signtool remove /s ' + path); | ||
| } catch (e) { | ||
| console.warn(`Failed to remove signature: ${e.message}`); | ||
| } | ||
| } else { | ||
| run('codesign --remove-signature ' + path); | ||
| } | ||
| } | ||
| function injectFuse(target, blob) { | ||
| let args; | ||
| if (isMac) { | ||
| args = [`"${target}"`, 'NODE_SEA_BLOB', blob, '--sentinel-fuse', NODE_SEA_FUSE, '--macho-segment-name', 'NODE_SEA']; | ||
| } else { | ||
| args = [target, 'NODE_SEA_BLOB', blob, '--sentinel-fuse', NODE_SEA_FUSE]; | ||
| } | ||
| run('npx', ['--yes', 'postject', ...args]); | ||
| } | ||
| function sign(bin) { | ||
| if (isWindows) { | ||
| try { | ||
| run('signtool', ['sign', '/fd', 'SHA256', bin]); | ||
| } catch (e) { | ||
| console.warn(`Failed to sign: ${e.message}`); | ||
| } | ||
| } else if (isMac) { | ||
| run('codesign', ['--sign', '-', bin]); | ||
| } | ||
| } | ||
| function buildNode(input, output, name, resources) { | ||
| const assets = resources ? JSON.parse(readFileSync(resources, 'utf-8')) : {}; | ||
| const configPath = join(output, 'sea-config.json'); | ||
| const binPath = copyNode(output, name); | ||
| writeSeaConfig(input, configPath, assets); | ||
| const blob = generateBlob(configPath); | ||
| removeSignature(binPath); | ||
| injectFuse(binPath, blob); | ||
| sign(binPath); | ||
| return binPath; | ||
| } | ||
| function buildDeno(input, output, name) { | ||
| const ext = isWindows ? '.exe' : ''; | ||
| const outfile = join(output, name + ext); | ||
| run('deno', ['compile', '--allow-all', '--no-check', '--output', `"${outfile}"`, `"${input}"`]); | ||
| return outfile; | ||
| } | ||
| function buildBun(input, output, name) { | ||
| const ext = isWindows ? '.exe' : ''; | ||
| const outfile = join(output, name + ext); | ||
| run('bun', ['build', '--compile', `"${input}"`, '--outfile', `"${outfile}"`]); | ||
| // Bun auto-appends .exe on Windows even if already present, normalise | ||
| return outfile; | ||
| } | ||
| export function build(input, output, name, resources, runtime = 'node') { | ||
| if (!existsSync(input)) { | ||
| throw new Error('Input file does not exist'); | ||
| } | ||
| if (resources && !existsSync(resources)) { | ||
| throw new Error('Resources file does not exist'); | ||
| } | ||
| if (!existsSync(output)) { | ||
| mkdirSync(output, { recursive: true }); | ||
| } | ||
| switch (runtime) { | ||
| case 'deno': | ||
| return buildDeno(input, output, name); | ||
| case 'bun': | ||
| return buildBun(input, output, name); | ||
| case 'node': | ||
| return buildNode(input, output, name, resources); | ||
| default: | ||
| throw new Error(`Unknown runtime: "${runtime}". Must be one of: node, deno, bun`); | ||
| } | ||
| } |
| #!/usr/bin/env node | ||
| import { readFile } from 'node:fs/promises'; | ||
| import { parseArgs, styleText } from 'node:util'; | ||
| import { join } from 'node:path'; | ||
| import { stripIndents } from './utils.mjs'; | ||
| import { build } from './build.mjs'; | ||
| async function readPackageJSON() { | ||
| const packageJSON = await readFile(join(import.meta.dirname, '..', 'package.json'), 'utf-8'); | ||
| return JSON.parse(packageJSON); | ||
| } | ||
| const { version, description, main } = await readPackageJSON(); | ||
| function inferRuntime() { | ||
| if (typeof globalThis.Bun !== 'undefined') return 'bun'; | ||
| if (typeof globalThis.Deno !== 'undefined') return 'deno'; | ||
| return 'node'; | ||
| } | ||
| const defaultRuntime = inferRuntime(); | ||
| const options = { | ||
| help: { type: 'boolean', short: 'h', description: 'Show help' }, | ||
| version: { type: 'boolean', short: 'v', description: 'Show version' }, | ||
| build: { | ||
| type: 'boolean', | ||
| short: 'b', | ||
| description: 'Build the project into a standalone executable', | ||
| }, | ||
| runtime: { | ||
| type: 'string', | ||
| short: 'R', | ||
| default: defaultRuntime, | ||
| description: 'Runtime to use for compilation (node, deno, bun)', | ||
| }, | ||
| name: { | ||
| type: 'string', | ||
| short: 'n', | ||
| default: 'webviewjs', | ||
| description: 'Project name', | ||
| }, | ||
| output: { | ||
| type: 'string', | ||
| short: 'o', | ||
| default: join(process.cwd(), 'dist'), | ||
| description: 'Output directory', | ||
| }, | ||
| input: { | ||
| type: 'string', | ||
| short: 'i', | ||
| default: join(process.cwd(), main), | ||
| description: 'Entry file', | ||
| }, | ||
| resources: { | ||
| type: 'string', | ||
| short: 'r', | ||
| description: 'Resources mapping json file path (node runtime only)', | ||
| }, | ||
| 'dry-run': { | ||
| type: 'boolean', | ||
| short: 'd', | ||
| description: 'Dry run', | ||
| }, | ||
| }; | ||
| const args = parseArgs({ | ||
| strict: true, | ||
| args: process.argv.slice(2), | ||
| options, | ||
| }); | ||
| let stdErr = false; | ||
| const logger = (message) => { | ||
| console.log(message); | ||
| process.exit(+stdErr); | ||
| }; | ||
| const defaultValuesOptionNames = new Set(Object.keys(options).filter((k) => !!options[k].default)); | ||
| if (!Object.keys(args.values).filter((k) => !defaultValuesOptionNames.has(k)).length) { | ||
| args.values.help = true; | ||
| stdErr = true; | ||
| } | ||
| if (args.values.help) { | ||
| const message = stripIndents`WebviewJS: ${styleText('greenBright', description)} | ||
| ${styleText('dim', 'Usage:')} ${styleText('greenBright', 'webview [options]')} | ||
| ${styleText('dim', 'Options:')} | ||
| ${Object.entries(options) | ||
| .map(([name, { short, default: defaultValue, type }]) => { | ||
| const msg = ` ${styleText('greenBright', ` -${short}, --${name}`)} - ${styleText('dim', options[name].description || `${type} option`)}`; | ||
| if (defaultValue) { | ||
| return `${msg} (default: ${styleText('blueBright', defaultValue)})`; | ||
| } | ||
| return msg; | ||
| }) | ||
| .join('\n')} | ||
| `; | ||
| logger(message); | ||
| } else if (args.values.version) { | ||
| logger( | ||
| `- WebviewJS v${version}\n- Node.js ${process.version}\n- Operating System: ${process.platform} ${process.arch}`, | ||
| ); | ||
| } else if (args.values.build) { | ||
| const isDry = !!args.values['dry-run']; | ||
| const { output, input, resources, runtime } = args.values; | ||
| const validRuntimes = ['node', 'deno', 'bun']; | ||
| if (!validRuntimes.includes(runtime)) { | ||
| console.error(styleText('red', `Invalid runtime "${runtime}". Must be one of: ${validRuntimes.join(', ')}`)); | ||
| process.exit(1); | ||
| } | ||
| if (isDry) { | ||
| logger(`Dry run: building ${input} to ${output} using ${runtime} runtime`); | ||
| } else { | ||
| const projectName = args.values.name || 'webviewjs'; | ||
| const target = build(input, output, prettify(projectName), resources, runtime); | ||
| logger( | ||
| styleText( | ||
| 'greenBright', | ||
| `\nBuilt ${input} to ${target}. You can now run the executable using ${styleText(['cyanBright', 'bold'], target)}`, | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
| function prettify(str) { | ||
| // remove stuff like @, /, whitespace, etc | ||
| return str.replace(/[^a-zA-Z0-9]/g, ''); | ||
| } |
| export function stripIndents(strings, ...values) { | ||
| let str = ''; | ||
| strings.forEach((string, i) => { | ||
| str += string + (values[i] || ''); | ||
| }); | ||
| return str.replace(/(\t)+/g, ' ').trim(); | ||
| } |
| # Application | ||
| The root object that owns the event loop and all windows. | ||
| ```js | ||
| import { Application } from '@webviewjs/webview'; | ||
| const app = new Application(); | ||
| ``` | ||
| ## Constructor | ||
| ```ts | ||
| new Application(options?: ApplicationOptions) | ||
| ``` | ||
| `ApplicationOptions` is accepted but currently unused; pass `null` or omit it. | ||
| ## Methods | ||
| ### `run(options?)` | ||
| Start the event pump. Calls `pumpEvents()` on a `setInterval` and returns immediately. | ||
| ```ts | ||
| app.run(options?: { interval?: number; ref?: boolean }): void | ||
| ``` | ||
| | Option | Default | Description | | ||
| | ---------- | ------- | ----------------------------------------------- | | ||
| | `interval` | `16` | Pump interval in ms | | ||
| | `ref` | `true` | If `false` the timer won't prevent process exit | | ||
| ### `stop()` | ||
| Clear the pump interval. The app object and windows remain valid. | ||
| ```ts | ||
| app.stop(): void | ||
| ``` | ||
| ### `exit()` | ||
| Stop the pump, hide all tracked windows, and mark the application as exited. Subsequent `pumpEvents()` calls return `false`. | ||
| ```ts | ||
| app.exit(): void | ||
| ``` | ||
| ### `pumpEvents()` | ||
| Process one batch of OS events without blocking. Returns `true` while alive, `false` when the app should exit. Normally called automatically by `run()`. | ||
| ```ts | ||
| app.pumpEvents(): boolean | ||
| ``` | ||
| ### `whenReady(options?)` | ||
| Resolve when wry emits its first native `resumed()` lifecycle callback. | ||
| `whenReady()` starts the event pump by default: | ||
| ```js | ||
| app.whenReady().then(() => { | ||
| const window = app.createBrowserWindow(); | ||
| window.createWebview({ url: 'https://example.com' }); | ||
| }); | ||
| ``` | ||
| ```ts | ||
| type ApplicationWhenReadyOptions = | ||
| | { autoRun?: true; interval?: number; ref?: boolean } | ||
| | { autoRun: false; interval?: never; ref?: never }; | ||
| app.whenReady(options?: ApplicationWhenReadyOptions): Promise<void> | ||
| app.isReady(): boolean | ||
| ``` | ||
| `autoRun` defaults to `true`; `interval` and `ref` are forwarded to `run()`. | ||
| Use `{ autoRun: false }` when manually calling `run()` or `pumpEvents()`. | ||
| Manual mode rejects `interval` and `ref` because no implicit timer is created. | ||
| Calls made after readiness still resolve asynchronously. | ||
| ## Application events | ||
| `Application` implements the standard Node.js `EventEmitter` API. Prefer this | ||
| interface for new Node.js code: | ||
| ```js | ||
| app.on('window-close-requested', (event) => { | ||
| console.log('window close requested', event); | ||
| }); | ||
| app.on('application-close-requested', () => { | ||
| app.exit(); | ||
| }); | ||
| app.on('custom-menu-click', ({ customMenuEvent }) => { | ||
| console.log(customMenuEvent.id, customMenuEvent.windowId); | ||
| }); | ||
| ``` | ||
| | Event | Fired when | | ||
| | ----------------------------- | --------------------------------------- | | ||
| | `window-close-requested` | A user requests that a window be closed | | ||
| | `application-close-requested` | The last window has been closed | | ||
| | `custom-menu-click` | A custom menu item is selected | | ||
| | `ready` | The native event loop is ready | | ||
| The usual `on`, `once`, `off`, `addListener`, `removeListener`, | ||
| `removeAllListeners`, `listenerCount`, `listeners`, `rawListeners`, `emit`, and | ||
| `eventNames` methods are available. Listener-registration and removal methods | ||
| are chainable. | ||
| See the runnable [application events example](../../examples/application-events.mjs). | ||
| ### Legacy `onEvent(handler)` / `bind(handler)` | ||
| Register a callback for application-level events. Both names are equivalent aliases. | ||
| ```ts | ||
| app.onEvent(handler: (event: ApplicationEvent) => void): void | ||
| ``` | ||
| `ApplicationEvent`: | ||
| ```ts | ||
| interface ApplicationEvent { | ||
| event: WebviewApplicationEvent; // enum value | ||
| customMenuEvent?: { id: string; windowId: number }; | ||
| } | ||
| ``` | ||
| `WebviewApplicationEvent` values: | ||
| | Value | Fired when | | ||
| | --------------------------- | -------------------------------------------------------- | | ||
| | `WindowCloseRequested` | User clicks the OS close button on a window | | ||
| | `ApplicationCloseRequested` | The last window was closed | | ||
| | `CustomMenuClick` | A custom menu item was clicked; see `customMenuEvent.id` | | ||
| | `Ready` | The native event loop emitted its first resume event | | ||
| ### `createBrowserWindow(options?)` | ||
| Create and return a new [`BrowserWindow`](./browser-window). | ||
| ```ts | ||
| app.createBrowserWindow(options?: BrowserWindowOptions): BrowserWindow | ||
| ``` | ||
| ### `createChildBrowserWindow(options?)` | ||
| Create a child/popup window. The webview fills a precise region inside the parent rather than the whole window. | ||
| ```ts | ||
| app.createChildBrowserWindow(options?: BrowserWindowOptions): BrowserWindow | ||
| ``` | ||
| ### `createWebContext(options?)` | ||
| Create an isolated browser-data context that can be shared by multiple webviews. | ||
| ```ts | ||
| app.createWebContext(options?: WebContextOptions): WebContext | ||
| ``` | ||
| Create contexts through the application rather than with `new WebContext()`. | ||
| See the [WebContext reference](./web-context). | ||
| ### `setMenu(options?)` | ||
| Set the global application menu. Pass `null` to remove it. | ||
| ```ts | ||
| app.setMenu(options?: MenuOptions): void | ||
| ``` | ||
| See [Menus guide](../guides/menus) for the full options shape. | ||
| This API remains supported. Compare its numeric event value with the exported | ||
| enum: | ||
| ```js | ||
| app.onEvent((event) => { | ||
| if (event.event === WebviewApplicationEvent.ApplicationCloseRequested) { | ||
| app.exit(); | ||
| } | ||
| }); | ||
| ``` | ||
| ### `Symbol.dispose` | ||
| `Application` implements the TC39 Explicit Resource Management protocol. Use `using` to guarantee cleanup: | ||
| ```js | ||
| { | ||
| using app = new Application(); | ||
| // … | ||
| } // app.exit() called automatically | ||
| ``` | ||
| ### Root-owned disposal | ||
| Resources created through an application are owned by that application. | ||
| `app.exit()`, `Symbol.dispose`, and application finalization dispose tray | ||
| icons, webviews, windows, web contexts, menus, and callbacks. Cleanup is | ||
| idempotent. Retained resource wrappers report `isDisposed() === true` and | ||
| reject subsequent method calls. Creating new resources after exit also fails. |
| # BrowserWindow | ||
| Represents an OS window. Created via `app.createBrowserWindow()`. | ||
| ## Creation options | ||
| ```ts | ||
| interface BrowserWindowOptions { | ||
| title?: string; // default: "WebviewJS" | ||
| width?: number; // default: 800 (physical px) | ||
| height?: number; // default: 600 (physical px) | ||
| x?: number; // initial left position | ||
| y?: number; // initial top position | ||
| logical?: boolean; // interpret size and position as logical pixels | ||
| resizable?: boolean; // default: true | ||
| visible?: boolean; // default: true | ||
| decorations?: boolean; // default: true (title bar + border) | ||
| transparent?: boolean; // default: false | ||
| maximized?: boolean; // default: false | ||
| maximizable?: boolean; // default: true | ||
| minimizable?: boolean; // default: true | ||
| focused?: boolean; // default: true | ||
| alwaysOnTop?: boolean; // default: false | ||
| alwaysOnBottom?: boolean; | ||
| contentProtection?: boolean; | ||
| visibleOnAllWorkspaces?: boolean; | ||
| fullscreen?: FullscreenType; // 'Exclusive' | 'Borderless' | ||
| menu?: MenuOptions; // per-window menu (overrides global) | ||
| showMenu?: boolean; // show the global menu on this window | ||
| } | ||
| ``` | ||
| ## Methods | ||
| ### `createWebview(options?)` | ||
| Attach a webview to the window. Returns a [`Webview`](./webview). | ||
| ```ts | ||
| win.createWebview(options?: WebviewOptions): Webview | ||
| ``` | ||
| Keep the returned `Webview` in application state for as long as the view is | ||
| needed. Do not rely on a discarded temporary wrapper. | ||
| Pass `options.webContext` to share browser data with other webviews. Pass | ||
| `options.navigationHandler` to synchronously allow or reject navigation. See | ||
| the [Webview reference](./webview). | ||
| ### Window state | ||
| ```ts | ||
| win.setTitle(title: string): void | ||
| win.setVisible(visible: boolean): void | ||
| win.show(): void | ||
| win.hide(): void | ||
| win.close(): void | ||
| win.setMinimized(value: boolean): void | ||
| win.setMaximized(value: boolean): void | ||
| win.setFullscreen(type: FullscreenType | null): void | ||
| win.focus(): void | ||
| win.requestRedraw(): void | ||
| ``` | ||
| ### Size & position | ||
| ```ts | ||
| // Pass true for logical pixels. The default is physical pixels. | ||
| win.getInnerSize(logical?: boolean): Dimensions | ||
| win.getOuterSize(logical?: boolean): Dimensions | ||
| win.setSize(width: number, height: number, logical?: boolean): Dimensions | null | ||
| win.setMinSize(width: number, height: number, logical?: boolean): void | ||
| win.setMaxSize(width: number, height: number, logical?: boolean): void | ||
| win.getPosition(logical?: boolean): Position | ||
| win.setPosition(x: number, y: number, logical?: boolean): void | ||
| win.center(): void // center on current monitor | ||
| win.scaleFactor(): number // device-pixel ratio | ||
| ``` | ||
| ### Cursor | ||
| ```ts | ||
| win.setCursor(cursor: CursorType): void | ||
| win.setCursorVisible(visible: boolean): void | ||
| win.setCursorPosition(x: number, y: number): void // logical px, relative to window | ||
| win.setIgnoreCursorEvents(ignore: boolean): void // click-through (Win/macOS) | ||
| ``` | ||
| **`CursorType` values:** | ||
| `Default`, `Crosshair`, `Hand`, `Arrow`, `Move`, `Text`, `Wait`, `Help`, `Progress`, | ||
| `NotAllowed`, `ContextMenu`, `Cell`, `VerticalText`, `Alias`, `Copy`, `NoDrop`, | ||
| `Grab`, `Grabbing`, `ZoomIn`, `ZoomOut`, | ||
| `ResizeEast`, `ResizeNorth`, `ResizeNorthEast`, `ResizeNorthWest`, | ||
| `ResizeSouth`, `ResizeSouthEast`, `ResizeSouthWest`, `ResizeWest`, | ||
| `ResizeEastWest`, `ResizeNorthSouth`, `ResizeNorthEastSouthWest`, | ||
| `ResizeNorthWestSouthEast`, `ResizeColumn`, `ResizeRow`, `AllScroll` | ||
| ### Decorations & behaviour | ||
| ```ts | ||
| win.setResizable(resizable: boolean): void | ||
| win.setMinimizable(minimizable: boolean): void | ||
| win.setMaximizable(maximizable: boolean): void | ||
| win.setClosable(closable: boolean): void | ||
| win.setAlwaysOnTop(always: boolean): void | ||
| win.setAlwaysOnBottom(always: boolean): void | ||
| win.setContentProtection(enabled: boolean): void | ||
| win.setDecorations(decorated: boolean): void | ||
| win.setSkipTaskbar(skip: boolean): void // Windows and Linux | ||
| ``` | ||
| ### Icon & progress | ||
| ```ts | ||
| win.setWindowIcon(rgba: Buffer, width: number, height: number): void | ||
| win.setProgressBar(progress: JsProgressBar): void | ||
| ``` | ||
| ### Windows extensions | ||
| These methods call Tao's `WindowExtWindows` API on Windows and return neutral | ||
| results or do nothing on other platforms: | ||
| Creation options expose the matching native attributes: | ||
| ```ts | ||
| windowsOwnerWindow?: bigint | ||
| windowsTaskbarIcon?: TrayIconImage | ||
| windowsNoRedirectionBitmap?: boolean | ||
| windowsDragAndDrop?: boolean | ||
| windowsSkipTaskbar?: boolean | ||
| windowsClassName?: string | ||
| windowsUndecoratedShadow?: boolean | ||
| ``` | ||
| Use the existing `menu` option instead of a raw Win32 `HMENU`. | ||
| ```ts | ||
| win.setEnable(enabled: boolean): void | ||
| win.setTaskbarIcon(data: Buffer, width?: number, height?: number): void | ||
| win.removeTaskbarIcon(): void | ||
| win.setUndecoratedShadow(shadow: boolean): void | ||
| win.getNativeHandleAnyThread(): bigint | ||
| ``` | ||
| Taskbar icon input follows `setWindowIcon`. | ||
| ### macOS creation options | ||
| ```ts | ||
| macosMovableByWindowBackground?: boolean | ||
| macosTitlebarTransparent?: boolean | ||
| macosTitleHidden?: boolean | ||
| macosTitlebarHidden?: boolean | ||
| macosTitlebarButtonsHidden?: boolean | ||
| macosFullsizeContentView?: boolean | ||
| macosDisallowHidpi?: boolean | ||
| macosHasShadow?: boolean | ||
| macosTabbingIdentifier?: string | ||
| ``` | ||
| ### macOS runtime extensions | ||
| ```ts | ||
| win.simpleFullscreen(): boolean | ||
| win.setSimpleFullscreen(fullscreen: boolean): boolean | ||
| win.hasShadow(): boolean | ||
| win.setHasShadow(value: boolean): void | ||
| win.setTabbingIdentifier(identifier: string): void | ||
| win.tabbingIdentifier(): string | ||
| win.isDocumentEdited(): boolean | ||
| win.setDocumentEdited(edited: boolean): void | ||
| ``` | ||
| ### Linux runtime extensions | ||
| `win.getWaylandSurface()` uses Tao's raw-window-handle support and returns the | ||
| native Wayland surface pointer as a bigint, or `0n` when the window is not | ||
| using Wayland. | ||
| ### iOS options and runtime extensions | ||
| Creation options use the `ios` prefix: `iosScaleFactor`, | ||
| `iosValidOrientations`, `iosPrefersHomeIndicatorHidden`, | ||
| `iosDeferredSystemGestureEdges`, and `iosPrefersStatusBarHidden`. | ||
| Runtime methods expose scale factor, valid orientations, home-indicator and | ||
| status-bar preferences, and deferred system-gesture edges through Tao's iOS | ||
| extensions. | ||
| Screen edges use a bitmask: top `1`, left `2`, bottom `4`, right `8`. | ||
| ### Android runtime extensions | ||
| ```ts | ||
| win.androidContentRect(): AndroidContentRect | ||
| win.androidConfig(): string | ||
| ``` | ||
| `androidConfig()` provides the current native configuration as a diagnostic | ||
| string. Runtime methods on unsupported platforms return neutral values or do | ||
| nothing. | ||
| `JsProgressBar`: | ||
| ```ts | ||
| interface JsProgressBar { | ||
| state?: ProgressBarState; // 'None' | 'Normal' | 'Indeterminate' | 'Paused' | 'Error' | ||
| progress?: number; // 0-100 | ||
| } | ||
| ``` | ||
| ### Theme | ||
| ```ts | ||
| win.setTheme(theme: Theme): void | ||
| ``` | ||
| ### Menu | ||
| Set `menu` in `BrowserWindowOptions` for a per-window menu. Set `showMenu` to | ||
| use the application menu. See the [Menus guide](../guides/menus). | ||
| ### Custom protocols | ||
| Register a URL-scheme handler before creating the webview. Must be called before `createWebview()`. | ||
| ```ts | ||
| win.registerProtocol( | ||
| name: string, | ||
| handler: (request: CustomProtocolRequest) => | ||
| CustomProtocolResponse | Promise<CustomProtocolResponse> | ||
| ): void | ||
| ``` | ||
| The handler may perform asynchronous file, database, or network work. See [Custom Protocols guide](../guides/custom-protocols). | ||
| ### File dialogs | ||
| ```ts | ||
| win.openFileDialog(options?: FileDialogOptions): Promise<string[]> | ||
| ``` | ||
| ```ts | ||
| interface FileDialogOptions { | ||
| multiple?: boolean; | ||
| title?: string; | ||
| defaultPath?: string; | ||
| filters?: Array<{ name: string; extensions: string[] }>; | ||
| } | ||
| ``` | ||
| ### Monitor info | ||
| ```ts | ||
| win.currentMonitor(): Monitor | null | ||
| win.primaryMonitor(): Monitor | null | ||
| win.availableMonitors(): Monitor[] | ||
| ``` | ||
| ```ts | ||
| interface Monitor { | ||
| name?: string; | ||
| scaleFactor: number; | ||
| size: Dimensions; | ||
| position: Position; | ||
| videoModes: VideoMode[]; | ||
| } | ||
| ``` | ||
| ### Identity | ||
| ```ts | ||
| win.id(): number // stable numeric id within this process | ||
| win.isChildWindow(): boolean | ||
| win.getNativeHandle(): bigint | ||
| ``` | ||
| `getNativeHandle()` returns the platform-native handle as a bigint pointer | ||
| value: HWND on Windows, NSView on macOS, XID on X11, or `wl_surface` on | ||
| Wayland. It returns `0` when no supported handle is available. Treat this as a | ||
| borrowed value and do not destroy it. | ||
| ### State and geometry properties | ||
| ```ts | ||
| win.width: number // inner width in physical pixels | ||
| win.height: number // inner height in physical pixels | ||
| win.x: number // outer x position in physical pixels | ||
| win.y: number // outer y position in physical pixels | ||
| win.getTitle: string | ||
| win.isFocused(): boolean | ||
| win.isVisible(): boolean | ||
| win.isDecorated(): boolean | ||
| win.isClosable(): boolean | ||
| win.isMaximizable(): boolean | ||
| win.isMinimizable(): boolean | ||
| win.isMaximized(): boolean | ||
| win.isMinimized(): boolean | ||
| win.isResizable(): boolean | ||
| ``` | ||
| ## Window events (EventEmitter) | ||
| `BrowserWindow` extends Node's `EventEmitter`. Use the standard `.on()`, | ||
| `.once()`, `.off()` / `.removeListener()`, `.removeAllListeners()` API. | ||
| ```ts | ||
| win.on('resize', ({ width, height }) => { … }) | ||
| win.on('move', ({ x, y }) => { … }) | ||
| win.on('close', () => { … }) | ||
| win.on('focus', () => { … }) | ||
| win.on('blur', () => { … }) | ||
| win.on('mouse-enter', ({ x, y }) => { … }) | ||
| win.on('mouse-leave', () => { … }) | ||
| win.on('mouse-move', ({ x, y }) => { … }) | ||
| win.on('mouse-down', ({ x, y, button }) => { … }) // button: 0=left 1=middle 2=right | ||
| win.on('mouse-up', ({ x, y, button }) => { … }) | ||
| win.on('scroll', ({ deltaX, deltaY })=> { … }) | ||
| win.on('key-down', ({ key, code, modifiers, isRepeat }) => { … }) | ||
| win.on('key-up', ({ key, code, modifiers, isRepeat }) => { … }) | ||
| win.on('file-drop', ({ files }) => { … }) | ||
| win.on('file-hover', ({ files }) => { … }) | ||
| win.on('file-hover-cancelled', () => { … }) | ||
| win.on('scale-factor-changed', ({ scaleFactor }) => { … }) | ||
| win.on('theme-changed', ({ text }) => { … }) | ||
| win.on('ime', ({ text, phase }) => { … }) | ||
| win.on('touch', ({ x, y, touchId, phase }) => { … }) | ||
| ``` | ||
| All positional values (`x`, `y`, `width`, `height`, `deltaX`, `deltaY`) are in | ||
| **physical pixels** at the current DPI. Divide by `win.scaleFactor()` to | ||
| convert to logical (CSS) pixels. | ||
| Scroll deltas from a pixel-precise input device (trackpad) are passed through | ||
| as-is; line-scroll deltas (mouse wheel) are multiplied by 20 to produce an | ||
| equivalent pixel distance. | ||
| IME phases are `enabled`, `preedit`, `commit`, or `disabled`. Touch phases are | ||
| `started`, `moved`, `ended`, or `cancelled`. | ||
| See the runnable [application events example](../../examples/application-events.mjs). | ||
| ### Undecorated-window resize | ||
| Windows created with `{ decorations: false, resizable: true }` use Tao's native | ||
| platform behavior for resizing. | ||
| ```ts | ||
| const win = app.createBrowserWindow({ decorations: false, resizable: true }); | ||
| // Resize works without extra code. | ||
| ``` | ||
| ## Disposal | ||
| Call `win.dispose()` for early cleanup, or use `Symbol.dispose`. Disposal is | ||
| idempotent. `win.isDisposed()` reports its state. Disposing a window also | ||
| disposes its webviews. `app.exit()` disposes every window owned by the | ||
| application. |
| # Menu | ||
| WebviewJS uses [muda](https://github.com/tauri-apps/muda) for native menu bars. | ||
| ## Setting a global menu | ||
| ```js | ||
| app.setMenu({ | ||
| items: [ | ||
| { | ||
| label: 'File', | ||
| submenu: { | ||
| items: [ | ||
| { label: 'New', id: 'file-new', accelerator: 'CmdOrCtrl+N' }, | ||
| { label: 'Open', id: 'file-open', accelerator: 'CmdOrCtrl+O' }, | ||
| { role: 'separator' }, | ||
| { role: 'quit' }, | ||
| ], | ||
| }, | ||
| }, | ||
| { | ||
| label: 'Edit', | ||
| submenu: { | ||
| items: [ | ||
| { role: 'undo' }, | ||
| { role: 'redo' }, | ||
| { role: 'separator' }, | ||
| { role: 'cut' }, | ||
| { role: 'copy' }, | ||
| { role: 'paste' }, | ||
| { role: 'selectall' }, | ||
| ], | ||
| }, | ||
| }, | ||
| ], | ||
| }); | ||
| ``` | ||
| `setMenu()` is additive — call it again to replace the menu. Pass `null` to remove it. | ||
| ## Per-window menu | ||
| ```js | ||
| win.setMenu({ | ||
| items: [ | ||
| /* … */ | ||
| ], | ||
| }); | ||
| ``` | ||
| A per-window menu overrides the global menu for that window only. | ||
| ## Handling menu clicks | ||
| ```js | ||
| app.on('custom-menu-click', ({ customMenuEvent }) => { | ||
| console.log('clicked:', customMenuEvent.id); | ||
| }); | ||
| ``` | ||
| ## `MenuItemOptions` shape | ||
| ```ts | ||
| interface MenuItemOptions { | ||
| id?: string; // unique ID emitted in CustomMenuClick events | ||
| label?: string; // display text | ||
| enabled?: boolean; // default: true | ||
| accelerator?: string; // e.g. "CmdOrCtrl+S", "Alt+F4" | ||
| role?: string; // predefined system action (see below) | ||
| submenu?: MenuOptions; | ||
| } | ||
| ``` | ||
| ## Predefined roles | ||
| Roles map to native platform actions and are localised automatically. | ||
| | Role | Action | | ||
| | ---------------------------------------- | ---------------------------------- | | ||
| | `copy` | Copy selection | | ||
| | `paste` | Paste | | ||
| | `cut` | Cut | | ||
| | `undo` | Undo | | ||
| | `redo` | Redo | | ||
| | `selectall` / `select-all` | Select all | | ||
| | `separator` / `-` | Horizontal separator line | | ||
| | `minimize` | Minimise window | | ||
| | `maximize` | Maximise window | | ||
| | `fullscreen` | Toggle fullscreen | | ||
| | `close` / `closewindow` / `close-window` | Close window | | ||
| | `quit` | Quit application | | ||
| | `about` | Show about dialog | | ||
| | `hide` | Hide application (macOS) | | ||
| | `hideothers` / `hide-others` | Hide other apps (macOS) | | ||
| | `showall` / `show-all` | Show all apps (macOS) | | ||
| | `services` | Services submenu (macOS) | | ||
| | `bringalltofront` / `bring-all-to-front` | Bring all windows to front (macOS) | | ||
| ## Accelerator syntax | ||
| ``` | ||
| CmdOrCtrl+S → Cmd+S on macOS, Ctrl+S elsewhere | ||
| Alt+F4 | ||
| Shift+CmdOrCtrl+Z | ||
| F5 | ||
| ``` | ||
| ## Platform notes | ||
| | Platform | Behaviour | | ||
| | ----------- | ------------------------------------------------------------------------------- | | ||
| | **Windows** | Menu bar attached to each window's title bar | | ||
| | **macOS** | Single app-level menu bar at the top of the screen | | ||
| | **Linux** | Per-window GTK menu bar attached through Tao's GTK window and default container | |
| # Notification | ||
| Displays a native desktop notification through | ||
| [notify-rust](https://docs.rs/notify-rust/latest/notify_rust/). | ||
| ```js | ||
| import { Notification } from '@webviewjs/webview'; | ||
| const notification = new Notification('Build complete', { | ||
| body: 'The release executable is ready.', | ||
| icon: './assets/app.png', | ||
| requireInteraction: true, | ||
| }); | ||
| notification.on('show', () => console.log('shown')); | ||
| notification.on('click', () => console.log('clicked')); | ||
| notification.on('close', () => console.log('closed')); | ||
| notification.on('error', ({ error }) => console.error(error)); | ||
| ``` | ||
| ## Permissions | ||
| Native application notifications do not use the browser permission model: | ||
| ```js | ||
| Notification.permission; // "granted" | ||
| await Notification.requestPermission(); // "granted" | ||
| ``` | ||
| `requestPermission()` is a JavaScript compatibility stub and never prompts. | ||
| ## Constructor | ||
| ```ts | ||
| new Notification(title: string, options?: NotificationOptions) | ||
| ``` | ||
| The API accepts familiar Web Notification options: | ||
| ```ts | ||
| interface NotificationOptions { | ||
| body?: string; | ||
| icon?: string; | ||
| image?: string | Buffer; | ||
| badge?: string; | ||
| tag?: string; | ||
| data?: unknown; | ||
| dir?: 'auto' | 'ltr' | 'rtl'; | ||
| lang?: string; | ||
| renotify?: boolean; | ||
| requireInteraction?: boolean; | ||
| persistent?: boolean; | ||
| actions?: NotificationAction[]; | ||
| silent?: boolean; | ||
| timestamp?: number; | ||
| vibrate?: number | number[]; | ||
| } | ||
| interface NotificationAction { | ||
| action: string; | ||
| title: string; | ||
| icon?: string; | ||
| } | ||
| ``` | ||
| notify-rust receives `title`, `body`, `icon`, `image`, and | ||
| `requireInteraction`. Persistent notification actions map their `action` and | ||
| `title` fields to native action buttons. Action icons are retained as readonly | ||
| instance data, but notify-rust does not currently expose per-action icons. | ||
| The remaining values are retained as readonly instance properties for API | ||
| familiarity but are not currently mapped to native backend features. | ||
| `icon` can be a platform icon name or file path. `image` accepts either a local | ||
| file path or a `Buffer` containing an encoded image: | ||
| ```js | ||
| import { readFile } from 'node:fs/promises'; | ||
| const image = await readFile('./assets/notification.png'); | ||
| const notification = new Notification('Image ready', { image }); | ||
| ``` | ||
| PNG, JPEG, WebP, GIF, BMP, ICO, TIFF, and other formats enabled by the Rust | ||
| `image` crate are decoded from buffers. Invalid encoded data emits `error` | ||
| without emitting `show`. | ||
| Linux sends decoded pixels directly to the notification server. Windows and | ||
| macOS use a temporary PNG because their native notification backends require a | ||
| file path. WebviewJS retains and removes that file with the notification | ||
| lifecycle. Remote URL strings are not downloaded and must be fetched into a | ||
| Buffer by application code first. Exact presentation varies by operating | ||
| system and notification server. | ||
| ## Persistent notifications and actions | ||
| Notifications are non-persistent by default. Their native callbacks do not | ||
| keep the Node.js process alive. | ||
| Set `persistent: true` when the notification must remain interactive after the | ||
| rest of the application has no active work: | ||
| ```js | ||
| const notification = new Notification('Download complete', { | ||
| body: 'The archive is ready.', | ||
| persistent: true, | ||
| actions: [ | ||
| { action: 'open', title: 'Open' }, | ||
| { action: 'dismiss', title: 'Dismiss' }, | ||
| ], | ||
| }); | ||
| notification.on('click', ({ action }) => { | ||
| if (action === 'open') { | ||
| // Open the downloaded archive. | ||
| } | ||
| }); | ||
| ``` | ||
| Actions follow the familiar persistent Web Notification shape. A non-empty | ||
| `actions` array requires `persistent: true`; otherwise the constructor throws | ||
| a `TypeError`. A default notification click emits `action: ""`, while clicking | ||
| an action button emits its action identifier. | ||
| Because WebviewJS does not have a service worker to restart, a persistent | ||
| notification keeps the Node.js process alive until the native backend reports | ||
| an interaction or closure. On platforms where notify-rust cannot close a | ||
| notification programmatically, calling `close()` cannot release that wait. | ||
| ## Events | ||
| Notification instances provide the same EventEmitter methods as other | ||
| WebviewJS event sources. They also support `onclick`, `onclose`, `onerror`, and | ||
| `onshow` properties. | ||
| | Event | Behavior | | ||
| | ------- | ---------------------------------------------------------------- | | ||
| | `show` | The native backend accepted the notification | | ||
| | `click` | The backend reported default activation or a notification action | | ||
| | `close` | The backend reported dismissal, expiry, or native closure | | ||
| | `error` | Display or response handling failed | | ||
| Interaction event availability depends on the native backend and desktop | ||
| notification server. The event object contains `type`, `target`, optional | ||
| `action`, and optional `error`. | ||
| On Windows, WebviewJS checks the global toast setting before submission. If | ||
| notifications are disabled in Windows Settings, the instance emits `error` | ||
| instead of `show`. Other operating-system policies such as Do Not Disturb can | ||
| still suppress presentation after the native backend accepts a notification. | ||
| ## Closing | ||
| ```js | ||
| notification.close(); | ||
| ``` | ||
| Programmatic close is supported where notify-rust exposes it. Currently this | ||
| is available through the Linux/XDG backend. The method is safe but performs no | ||
| native close operation on backends where notify-rust does not expose one. | ||
| ## Mobile | ||
| Android and iOS expose the JavaScript API but do not display a notification or | ||
| emit native lifecycle events. | ||
| See the runnable [notification example](../../examples/notification.mjs). |
| # System Tray | ||
| Create tray icons through `Application` so creation occurs on the event-loop thread. | ||
| ```js | ||
| const tray = app.createTrayIcon({ | ||
| id: 'main', | ||
| icon: { data: rgba, width: 16, height: 16 }, | ||
| tooltip: 'My application', | ||
| menu: { items: [{ id: 'quit', label: 'Quit' }] }, | ||
| }); | ||
| ``` | ||
| Icon data may be raw RGBA bytes with dimensions or an encoded image without dimensions. | ||
| ## Methods | ||
| ```ts | ||
| tray.id: string | ||
| tray.setIcon(data: Buffer, width?: number, height?: number): void | ||
| tray.removeIcon(): void | ||
| tray.setMenu(menu?: MenuOptions): void | ||
| tray.setTooltip(tooltip?: string): void | ||
| tray.setTitle(title?: string): void | ||
| tray.setVisible(visible: boolean): void | ||
| tray.setIconAsTemplate(value: boolean): void | ||
| tray.setShowMenuOnLeftClick(value: boolean): void | ||
| tray.setShowMenuOnRightClick(value: boolean): void | ||
| tray.showMenu(): void | ||
| tray.rect(): TrayRect | null | ||
| ``` | ||
| ## Events | ||
| `TrayIcon` implements Node.js EventEmitter methods for `click`, | ||
| `double-click`, `enter`, `move`, and `leave`. Linux does not emit pointer | ||
| events. Tray menu selections use the application `custom-menu-click` event. | ||
| Title is unsupported on Windows. Tooltip and click-menu configuration are | ||
| unsupported on Linux. Template icons are macOS-only. | ||
| ## Lifetime and disposal | ||
| Keep the wrapper strongly referenced while you need its methods or listeners. | ||
| The root `Application` owns the native tray icon, so `app.exit()` removes it | ||
| even when the wrapper remains reachable. | ||
| Call `tray.dispose()` for early removal, or use `Symbol.dispose`. | ||
| `tray.isDisposed()` reports whether the icon has been disposed. | ||
| See the runnable [tray example](../../examples/tray.mjs). |
| # Types Reference | ||
| Common types shared across the API. | ||
| ## `Dimensions` | ||
| ```ts | ||
| interface Dimensions { | ||
| width: number; | ||
| height: number; | ||
| } | ||
| ``` | ||
| ## `Position` | ||
| ```ts | ||
| interface Position { | ||
| x: number; | ||
| y: number; | ||
| } | ||
| ``` | ||
| ## `WebviewBounds` | ||
| Logical-pixel rectangle used by child webview positioning. | ||
| ```ts | ||
| interface WebviewBounds { | ||
| x: number; | ||
| y: number; | ||
| width: number; | ||
| height: number; | ||
| } | ||
| ``` | ||
| ## `WebviewCookie` | ||
| ```ts | ||
| interface WebviewCookie { | ||
| name: string; | ||
| value: string; | ||
| domain?: string; | ||
| path?: string; | ||
| httpOnly?: boolean; | ||
| secure?: boolean; | ||
| sameSite?: 'strict' | 'lax' | 'none'; | ||
| } | ||
| ``` | ||
| ## `HeaderData` | ||
| ```ts | ||
| interface HeaderData { | ||
| key: string; | ||
| value?: string; | ||
| } | ||
| ``` | ||
| ## `CustomProtocolRequest` | ||
| This is the legacy native plain-object shape. The public | ||
| `BrowserWindow.registerProtocol()` callback receives a standard global | ||
| Fetch API `Request`. | ||
| ```ts | ||
| interface CustomProtocolRequest { | ||
| url: string; // full URL, e.g. "app://localhost/index.html" | ||
| method: string; // "GET", "POST", etc. | ||
| headers: HeaderData[]; | ||
| body?: Buffer; // present for POST / PUT | ||
| } | ||
| ``` | ||
| ## `CustomProtocolResponse` | ||
| ```ts | ||
| interface CustomProtocolResponse { | ||
| body: Buffer; // response bytes (required) | ||
| mimeType?: string; // default: "application/octet-stream" | ||
| statusCode?: number; // default: 200 | ||
| headers?: HeaderData[]; // extra response headers | ||
| } | ||
| ``` | ||
| Public protocol handlers may return this legacy shape or a standard global | ||
| Fetch API `Response`. | ||
| ## `WebContextOptions` | ||
| ```ts | ||
| interface WebContextOptions { | ||
| dataDirectory?: string; | ||
| allowsAutomation?: boolean; | ||
| } | ||
| ``` | ||
| See the [WebContext reference](./web-context). | ||
| ## Webview event payloads | ||
| ```ts | ||
| interface WebviewPageLoadEvent { | ||
| event: number; | ||
| url?: string; | ||
| } | ||
| interface WebviewTitleChangedEvent { | ||
| event: number; | ||
| title?: string; | ||
| } | ||
| interface WebviewDownloadEvent { | ||
| event: number; | ||
| url?: string; | ||
| success?: boolean; | ||
| } | ||
| interface WebviewNavigationEvent { | ||
| event: number; | ||
| url?: string; | ||
| } | ||
| interface WebviewNewWindowEvent { | ||
| event: number; | ||
| url?: string; | ||
| } | ||
| ``` | ||
| ## `WebviewOptions.ipcName` | ||
| `ipcName?: string` adds a page-global alias for wry's built-in `window.ipc`. For example, `{ ipcName: 'bindings' }` makes `window.bindings.postMessage(...)` available before page scripts run. `window.ipc` remains available. | ||
| ## `SerializationError` | ||
| `webview.expose()` uses JSON serialization for static values, arguments, and returned values. Unsupported values reject with an error whose `name` is `SerializationError`. | ||
| ## `IpcMessage` | ||
| Received by the `webview.onIpcMessage()` callback. | ||
| ```ts | ||
| interface IpcMessage { | ||
| body: Buffer; | ||
| method: string; | ||
| headers: HeaderData[]; | ||
| uri: string; | ||
| } | ||
| ``` | ||
| ## `Monitor` | ||
| ```ts | ||
| interface Monitor { | ||
| name?: string; | ||
| scaleFactor: number; | ||
| size: Dimensions; | ||
| position: Position; | ||
| videoModes: VideoMode[]; | ||
| } | ||
| interface VideoMode { | ||
| size: Dimensions; | ||
| bitDepth: number; | ||
| refreshRate: number; | ||
| } | ||
| ``` | ||
| ## `ApplicationEvent` | ||
| ```ts | ||
| interface ApplicationEvent { | ||
| event: WebviewApplicationEvent; | ||
| customMenuEvent?: CustomMenuEvent; | ||
| } | ||
| interface CustomMenuEvent { | ||
| id: string; | ||
| windowId: number; | ||
| } | ||
| ``` | ||
| ## Enums | ||
| ### `WebviewApplicationEvent` | ||
| ```ts | ||
| enum WebviewApplicationEvent { | ||
| WindowCloseRequested = 'WindowCloseRequested', | ||
| ApplicationCloseRequested = 'ApplicationCloseRequested', | ||
| CustomMenuClick = 'CustomMenuClick', | ||
| } | ||
| ``` | ||
| ### `FullscreenType` | ||
| ```ts | ||
| enum FullscreenType { | ||
| Exclusive = 'Exclusive', | ||
| Borderless = 'Borderless', | ||
| } | ||
| ``` | ||
| ### `Theme` | ||
| ```ts | ||
| enum Theme { | ||
| Light = 'Light', | ||
| Dark = 'Dark', | ||
| } | ||
| ``` | ||
| ### `ProgressBarState` | ||
| ```ts | ||
| enum ProgressBarState { | ||
| None = 'None', | ||
| Normal = 'Normal', | ||
| Indeterminate = 'Indeterminate', | ||
| Paused = 'Paused', | ||
| Error = 'Error', | ||
| } | ||
| ``` | ||
| ### `CursorType` | ||
| See [BrowserWindow cursor section](./browser-window#cursor) for the full list. | ||
| ### `WindowEventType` | ||
| Numeric discriminant of the `event` field in each `BrowserWindowEventMap` | ||
| payload. Values correspond to the order declared in the Rust `WindowEventType` | ||
| enum and are mapped to string event names by the JS layer — normal user code | ||
| should key on the string name, not the integer. | ||
| | Value | String name | Payload fields | | ||
| | ----- | ------------- | --------------------------------------------- | | ||
| | 0 | `move` | `x`, `y` (physical px, outer window position) | | ||
| | 1 | `resize` | `width`, `height` (physical px, inner size) | | ||
| | 2 | `close` | — | | ||
| | 3 | `focus` | — | | ||
| | 4 | `blur` | — | | ||
| | 5 | `mouse-enter` | `x`, `y` (physical px, last cursor position) | | ||
| | 6 | `mouse-leave` | — | | ||
| | 7 | `mouse-move` | `x`, `y` (physical px) | | ||
| | 8 | `mouse-down` | `x`, `y`, `button` (0=left 1=middle 2=right) | | ||
| | 9 | `mouse-up` | `x`, `y`, `button` | | ||
| | 10 | `scroll` | `deltaX`, `deltaY` (physical px) | | ||
| ### `BrowserWindowEventMap` | ||
| ```ts | ||
| interface BrowserWindowEventMap { | ||
| move: { event: number; x: number; y: number }; | ||
| resize: { event: number; width: number; height: number }; | ||
| close: { event: number }; | ||
| focus: { event: number }; | ||
| blur: { event: number }; | ||
| 'mouse-enter': { event: number; x: number; y: number }; | ||
| 'mouse-leave': { event: number }; | ||
| 'mouse-move': { event: number; x: number; y: number }; | ||
| 'mouse-down': { event: number; x: number; y: number; button: number }; | ||
| 'mouse-up': { event: number; x: number; y: number; button: number }; | ||
| scroll: { event: number; deltaX: number; deltaY: number }; | ||
| } | ||
| ``` |
| # WebContext | ||
| A `WebContext` owns browser data and configuration shared by webviews. Use | ||
| separate contexts for isolated profiles, or reuse one context when webviews | ||
| need the same cookies, cache, local storage, and IndexedDB data. | ||
| ## Creation | ||
| Create a context through `Application`: | ||
| ```js | ||
| const context = app.createWebContext({ | ||
| dataDirectory: './browser-data', | ||
| allowsAutomation: false, | ||
| }); | ||
| ``` | ||
| ```ts | ||
| interface WebContextOptions { | ||
| dataDirectory?: string; | ||
| allowsAutomation?: boolean; | ||
| } | ||
| ``` | ||
| `new WebContext()` is not supported. Keep the context alive for at least as | ||
| long as every webview that uses it. | ||
| ## Using a context | ||
| Pass the context when creating each webview: | ||
| ```js | ||
| const first = firstWindow.createWebview({ | ||
| url: 'https://example.com', | ||
| webContext: context, | ||
| }); | ||
| const second = secondWindow.createWebview({ | ||
| url: 'https://example.com', | ||
| webContext: context, | ||
| }); | ||
| ``` | ||
| Both webviews use the same browser-data store. A webview created without | ||
| `webContext` uses its own default context. | ||
| See the runnable [web context example](../../examples/web-context.mjs). | ||
| ## Properties and methods | ||
| ```ts | ||
| context.dataDirectory: string | null | ||
| context.isCustomProtocolRegistered(scheme: string): boolean | ||
| context.setAllowsAutomation(enabled: boolean): void | ||
| ``` | ||
| `dataDirectory` reports the configured persistent data directory. | ||
| `isCustomProtocolRegistered()` checks the context's native protocol registry. | ||
| Automation is currently enforced only on Linux, where only one context can | ||
| allow automation at a time. Enable it only for controlled testing. | ||
| ## Disposal | ||
| Call `context.dispose()` for early cleanup, or use `Symbol.dispose`. Disposal | ||
| is idempotent. `context.isDisposed()` reports its state. `app.exit()` disposes | ||
| every context created through that application. |
| # Webview | ||
| Controls the embedded browser view attached to a `BrowserWindow`. Created via `win.createWebview()`. | ||
| Keep a strong JavaScript reference to each `Webview` for its intended | ||
| lifetime. This preserves access to methods and EventEmitter listeners. The | ||
| root `Application` owns the native webview and disposes it during `app.exit()`. | ||
| ## Creation options | ||
| ```ts | ||
| interface WebviewOptions { | ||
| url?: string; // URL to load on start | ||
| html?: string; // Inline HTML to render (mutually exclusive with url) | ||
| x?: number; // Left offset in logical pixels (child webviews only) | ||
| y?: number; // Top offset in logical pixels (child webviews only) | ||
| width?: number; // Width in logical pixels (child webviews only) | ||
| height?: number; // Height in logical pixels (child webviews only) | ||
| child?: boolean; // If true, position is relative to parent window | ||
| enableDevtools?: boolean; // Enable DevTools | ||
| transparent?: boolean; // Transparent background | ||
| incognito?: boolean; // Private mode (no persistent storage) | ||
| userAgent?: string; // Custom user-agent string | ||
| preload?: string; // JS injected before any page script runs | ||
| ipcName?: string; // Alias for window.ipc, for example window.bindings | ||
| webContext?: WebContext; // Shared browser data context | ||
| navigationHandler?: (url: string) => boolean; // allow or cancel navigation | ||
| } | ||
| ``` | ||
| > **Note on bounds:** For top-level webviews (not child), omit `x`/`y`/`width`/`height` so the webview fills the window and resizes with it automatically. Setting explicit bounds fixes the size, which causes the black-border artifact when the window is maximised. | ||
| ## Navigation | ||
| ```ts | ||
| webview.loadUrl(url: string): void | ||
| webview.loadHtml(html: string): void | ||
| webview.loadUrlWithHeaders(url: string, headers: HeaderData[]): void | ||
| webview.reload(): void | ||
| webview.url(): string | null // currently displayed URL | ||
| ``` | ||
| `navigationHandler` runs synchronously before each navigation. Return `false` | ||
| to cancel it. Keep the callback fast and do not return a Promise. A | ||
| `navigation` event is emitted whether the navigation is allowed or cancelled. | ||
| See the runnable [navigation handler example](../../examples/navigation-handler.mjs). | ||
| ## Events | ||
| `Webview` implements standard Node.js `EventEmitter` methods, including `on`, | ||
| `once`, `off`, `addListener`, `removeListener`, and `removeAllListeners`. | ||
| ```js | ||
| webview.on('page-load-started', ({ url }) => {}); | ||
| webview.on('page-load-finished', ({ url }) => {}); | ||
| webview.on('title-changed', ({ title }) => {}); | ||
| webview.on('download-started', ({ url }) => {}); | ||
| webview.on('download-completed', ({ url, success }) => {}); | ||
| webview.on('navigation', ({ url }) => {}); | ||
| webview.on('new-window', ({ url }) => {}); | ||
| ``` | ||
| The `new-window` event observes attempts from `window.open`, | ||
| `target="_blank"`, and equivalent browser actions. The request is allowed | ||
| after dispatch. Download events are observational and do not cancel downloads. | ||
| See the runnable [webview events example](../../examples/webview-events.mjs). | ||
| `HeaderData`: | ||
| ```ts | ||
| interface HeaderData { | ||
| key: string; | ||
| value?: string; | ||
| } | ||
| ``` | ||
| ## Script execution | ||
| ```ts | ||
| webview.evaluateScript(script: string): void | ||
| webview.evaluateScriptWithCallback(script: string, callback: (result: string) => void): void | ||
| ``` | ||
| ## Cookies | ||
| ```ts | ||
| webview.getCookies(url?: string): WebviewCookie[] | ||
| webview.setCookie(cookie: WebviewCookie): void | ||
| webview.deleteCookie(name: string, domain?: string, path?: string): void | ||
| webview.clearAllBrowsingData(): void | ||
| ``` | ||
| `WebviewCookie`: | ||
| ```ts | ||
| interface WebviewCookie { | ||
| name: string; | ||
| value: string; | ||
| domain?: string; | ||
| path?: string; | ||
| httpOnly?: boolean; | ||
| secure?: boolean; | ||
| sameSite?: 'strict' | 'lax' | 'none'; | ||
| } | ||
| ``` | ||
| ## Appearance | ||
| ```ts | ||
| webview.setBackgroundColor(r: number, g: number, b: number, a: number): void // 0-255 each | ||
| ``` | ||
| ## Bounds (child webviews) | ||
| For child webviews you can reposition or resize the view at runtime: | ||
| ```ts | ||
| webview.getBounds(): WebviewBounds | null | ||
| webview.setBounds(bounds: WebviewBounds): void | ||
| webview.width: number | null | ||
| webview.height: number | null | ||
| webview.x: number | null | ||
| webview.y: number | null | ||
| ``` | ||
| ```ts | ||
| interface WebviewBounds { | ||
| x: number; | ||
| y: number; | ||
| width: number; | ||
| height: number; | ||
| } | ||
| ``` | ||
| ## DevTools | ||
| ```ts | ||
| webview.openDevtools(): void | ||
| webview.closeDevtools(): void | ||
| webview.isDevtoolsOpen(): boolean | ||
| ``` | ||
| ## Focus | ||
| ```ts | ||
| webview.focus(): void // give keyboard focus to the webview | ||
| webview.focusParent(): void // return focus to the parent window | ||
| ``` | ||
| ## IPC | ||
| The page calls `window.ipc.postMessage(body)` to send a message to Node. | ||
| Node registers its handler with `webview.onIpcMessage(handler)`. | ||
| Set `ipcName: 'bindings'` to add `window.bindings` as an alias. `window.ipc` always remains available. | ||
| See [IPC guide](../guides/ipc-messaging) for a complete walkthrough. | ||
| ## `expose(name, target)` | ||
| Expose JSON static values and Node functions under a page global. Page functions always return Promises, even when the Node implementation is synchronous. | ||
| ```js | ||
| webview.expose('native', { | ||
| isCool: true, | ||
| readFile: async (path) => readFile(path, 'utf8'), | ||
| }); | ||
| ``` | ||
| ```js | ||
| // In the page | ||
| console.log(window.native.isCool); | ||
| const text = await window.native.readFile('/tmp/example.txt'); | ||
| ``` | ||
| Only enumerable own data properties are exposed. Getters and setters are ignored. Arguments, static values, and function results must be JSON-serializable. Cyclic structures, `BigInt`, functions as values, and `undefined` results are rejected with `SerializationError`. | ||
| The namespace must be a valid JavaScript identifier and can be exposed only once for a webview. See the runnable [expose example](../../examples/expose.mjs). | ||
| ## Custom protocols | ||
| Custom protocols are registered on the **`BrowserWindow`** before `createWebview()` is called: | ||
| ```js | ||
| win.registerProtocol('app', async (request) => { | ||
| // ... | ||
| return { statusCode: 200, body: Buffer.from('…'), mimeType: 'text/html' }; | ||
| }); | ||
| const webview = win.createWebview({ url: 'app://localhost/index.html' }); | ||
| ``` | ||
| See [Custom Protocols guide](../guides/custom-protocols) for full details. | ||
| ## Disposal | ||
| Call `webview.dispose()` for early cleanup, or use `Symbol.dispose`. Disposal | ||
| is idempotent. `webview.isDisposed()` reports its state. `app.exit()` also | ||
| disposes every webview created under that application. |
Sorry, the diff of this file is not supported yet
| # Event Loop | ||
| WebviewJS uses **Tao**'s non-blocking `pump_events()` integration so the GUI never blocks Node.js's event loop. | ||
| ## How it works | ||
| `app.run()` is a thin JS wrapper that sets up a `setInterval` calling `app.pumpEvents()` on every tick: | ||
| ```js | ||
| // Equivalent to what app.run() does | ||
| const timer = setInterval(() => { | ||
| if (!app.pumpEvents()) app.stop(); | ||
| }, 16); // ~60 FPS | ||
| ``` | ||
| Each call to `pumpEvents()` drains the OS message queue without waiting, then returns `true` if the app is still alive or `false` when it should shut down. | ||
| Because this runs inside a Node.js timer, all Node APIs (file I/O, network, child processes, etc.) work normally alongside the GUI. | ||
| ## Application readiness | ||
| `app.whenReady()` starts the event pump and resolves when Tao emits the first | ||
| native `Resumed` event: | ||
| ```js | ||
| app.whenReady().then(() => { | ||
| console.log('native application is ready'); | ||
| }); | ||
| ``` | ||
| Configure the implicit timer through readiness options: | ||
| ```js | ||
| app.whenReady({ interval: 33, ref: false }); | ||
| ``` | ||
| For manual control, disable auto-run and then start your own pump: | ||
| ```js | ||
| const ready = app.whenReady({ autoRun: false }); | ||
| app.pumpEvents(); | ||
| await ready; | ||
| ``` | ||
| `interval` and `ref` are not accepted when `autoRun` is `false`. | ||
| ## Controlling the interval | ||
| ```js | ||
| // 30 FPS (more CPU-friendly for simple apps) | ||
| app.run({ interval: 33 }); | ||
| // Let the process exit naturally even while the GUI is open | ||
| // (useful for scripts that should end when async work finishes) | ||
| app.run({ ref: false }); | ||
| ``` | ||
| | Option | Default | Description | | ||
| | ---------- | ------- | ----------------------------------------------------------------------- | | ||
| | `interval` | `16` | Milliseconds between event pumps (~60 FPS) | | ||
| | `ref` | `true` | If `false`, the timer is `unref()`'d so it won't keep the process alive | | ||
| ## Stopping manually | ||
| ```js | ||
| app.stop(); // clears the interval but leaves windows open | ||
| app.exit(); // stop() + hides all windows + marks app as exited | ||
| ``` | ||
| `stop()` without `exit()` is useful if you want to take over the loop yourself: | ||
| ```js | ||
| app.stop(); | ||
| // run a tight loop for a CPU-intensive frame: | ||
| while (frames-- > 0) app.pumpEvents(); | ||
| app.run(); // hand back to the interval | ||
| ``` | ||
| ## macOS note | ||
| On macOS the main thread must own the GUI. WebviewJS always runs the event loop on the main thread (the thread that called `new Application()`). Do **not** create Application or BrowserWindow from a worker thread. |
| # Installation | ||
| ## Requirements | ||
| | Platform | Requirement | | ||
| | -------- | ------------------------------------------------------------------------------- | | ||
| | Windows | WebView2 runtime (ships with Windows 11 and Edge; auto-installed on Windows 10) | | ||
| | macOS | macOS 10.15 Catalina or later (WebKit is built-in) | | ||
| | Linux | `libwebkit2gtk-4.1` and `libxdo` | | ||
| ### Linux dependency install | ||
| ```bash | ||
| # Debian / Ubuntu | ||
| sudo apt install libwebkit2gtk-4.1-dev libxdo-dev | ||
| # Fedora | ||
| sudo dnf install webkit2gtk4.1-devel libxdo-devel | ||
| # Arch | ||
| sudo pacman -S webkit2gtk-4.1 xdotool | ||
| ``` | ||
| ## NPM | ||
| ```bash | ||
| npm install @webviewjs/webview | ||
| ``` | ||
| ## Building from source | ||
| You need the Rust toolchain and the [napi-rs CLI](https://napi.rs/docs/introduction/getting-started). | ||
| ```bash | ||
| git clone https://github.com/webviewjs/webview | ||
| cd webview | ||
| npm install | ||
| npm run build # compiles Rust and generates JS bindings | ||
| ``` | ||
| The compiled native addon is placed in `<platform>-<arch>/` (e.g. `win32-x64-msvc/`) and `index.js` is updated automatically. |
| # Quick Start | ||
| ## Minimal example | ||
| ```js | ||
| import { Application, BrowserWindow } from '@webviewjs/webview'; | ||
| const app = new Application(); | ||
| const win = app.createBrowserWindow({ | ||
| title: 'My App', | ||
| width: 1024, | ||
| height: 768, | ||
| }); | ||
| const webview = win.createWebview({ url: 'https://example.com' }); | ||
| app.run(); | ||
| ``` | ||
| ## Loading local HTML | ||
| ```js | ||
| const webview = win.createWebview({ | ||
| html: '<h1>Hello from WebviewJS</h1>', | ||
| }); | ||
| ``` | ||
| ## Reacting to window events | ||
| ```js | ||
| app.on('window-close-requested', () => { | ||
| console.log('A window was closed'); | ||
| }); | ||
| app.on('application-close-requested', () => { | ||
| console.log('All windows closed; exiting'); | ||
| app.exit(); | ||
| }); | ||
| app.on('custom-menu-click', ({ customMenuEvent }) => { | ||
| console.log('Menu item clicked:', customMenuEvent.id); | ||
| }); | ||
| ``` | ||
| ## Retaining native handles | ||
| Keep strong references to windows, webviews, contexts, and tray icons while | ||
| you need their wrapper methods or event listeners. Store handles in | ||
| application state instead of discarding creation results. | ||
| The root `Application` owns their native resources. Calling `app.exit()` | ||
| disposes all root-created resources. Retained wrappers subsequently return | ||
| `true` from `isDisposed()` and reject further method calls. | ||
| ## IPC | ||
| ```js | ||
| const webview = win.createWebview({ | ||
| html: ` | ||
| <button onclick="window.ipc.postMessage('ping')">Ping</button> | ||
| `, | ||
| }); | ||
| webview.onIpcMessage((msg) => { | ||
| console.log('IPC body:', msg.body.toString()); | ||
| webview.evaluateScript('document.body.style.background = "lime"'); | ||
| }); | ||
| ``` | ||
| For asynchronous page-to-Node calls, use `webview.expose()`: | ||
| ```js | ||
| webview.expose('native', { | ||
| getGreeting: async (name) => `Hello, ${name}`, | ||
| }); | ||
| ``` | ||
| The page calls `await window.native.getGreeting('Ada')`. | ||
| ## Using `Symbol.dispose` (auto-cleanup) | ||
| ```js | ||
| { | ||
| using app = new Application(); | ||
| // … | ||
| } // app.exit() is called automatically | ||
| ``` | ||
| Each `BrowserWindow`, `Webview`, `WebContext`, and `TrayIcon` also supports | ||
| `dispose()` and `Symbol.dispose` for early cleanup. |
| # Building Standalone Executables | ||
| The `webview` CLI can package your application into a single self-contained executable — no Node.js, Deno, or Bun installation required on the target machine. | ||
| ## Quick start | ||
| ```bash | ||
| # Install globally (or use npx) | ||
| npm install -g @webviewjs/webview | ||
| # Build with your current runtime (Node.js by default) | ||
| webview --build --input src/index.js --name myapp | ||
| ``` | ||
| The executable is written to `dist/` by default. On Windows it gets a `.exe` extension automatically. | ||
| --- | ||
| ## Choosing a runtime | ||
| Use `--runtime` (short: `-R`) to select how the executable is compiled: | ||
| | Runtime | Flag | Tool used | | ||
| | ------- | ---------------- | --------------------- | | ||
| | Node.js | `--runtime node` | Node.js SEA (default) | | ||
| | Deno | `--runtime deno` | `deno compile` | | ||
| | Bun | `--runtime bun` | `bun build --compile` | | ||
| ### Node.js (default) | ||
| Node.js does not have a one-shot compile command, so the CLI automates the multi-step [Single Executable Application (SEA)](https://nodejs.org/api/single-executable-applications.html) workflow for you: | ||
| 1. Writes a `sea-config.json` in the output directory. | ||
| 2. Runs `node --experimental-sea-config sea-config.json` to produce `sea-prep.blob`. | ||
| 3. Copies the current `node` binary. | ||
| 4. Removes its existing signature (macOS / Windows). | ||
| 5. Injects the blob via `postject` (downloaded automatically with `npx`). | ||
| 6. Re-signs the binary (`codesign` on macOS, `signtool` on Windows — optional). | ||
| ```bash | ||
| webview --build --runtime node --input src/index.js --name myapp --output dist | ||
| ``` | ||
| #### Bundling assets (Node.js only) | ||
| Pass a JSON file mapping asset names to file paths via `--resources`: | ||
| ```json | ||
| { | ||
| "icon.png": "./assets/icon.png", | ||
| "config.json": "./config.json" | ||
| } | ||
| ``` | ||
| ```bash | ||
| webview --build --runtime node --resources assets.json --input src/index.js --name myapp | ||
| ``` | ||
| Inside your script, read them with the `node:sea` API: | ||
| ```js | ||
| const { getAsset } = require('node:sea'); | ||
| const iconBuffer = getAsset('icon.png'); | ||
| ``` | ||
| --- | ||
| ### Deno | ||
| Requires `deno` on your `PATH`. Uses `deno compile` which bundles everything into a single binary. | ||
| ```bash | ||
| webview --build --runtime deno --input src/index.ts --name myapp --output dist | ||
| ``` | ||
| The CLI runs: | ||
| ```bash | ||
| deno compile --allow-all --no-check --output dist/myapp src/index.ts | ||
| ``` | ||
| > `--allow-all` grants all permissions. Restrict them in your own build script if needed. | ||
| --- | ||
| ### Bun | ||
| Requires `bun` on your `PATH`. Uses `bun build --compile`. | ||
| ```bash | ||
| webview --build --runtime bun --input src/index.ts --name myapp --output dist | ||
| ``` | ||
| The CLI runs: | ||
| ```bash | ||
| bun build --compile src/index.ts --outfile dist/myapp | ||
| ``` | ||
| On Windows, Bun automatically appends `.exe` if not already present. | ||
| --- | ||
| ## All options | ||
| ``` | ||
| -b, --build Build the project into a standalone executable | ||
| -R, --runtime Runtime to use for compilation: node (default), deno, bun | ||
| -n, --name Executable name (default: webviewjs) | ||
| -i, --input Entry file (default: index.js in cwd) | ||
| -o, --output Output directory (default: dist/) | ||
| -r, --resources Resources mapping JSON file path (node runtime only) | ||
| -d, --dry-run Print what would be done without executing | ||
| -h, --help Show help | ||
| -v, --version Show version | ||
| ``` | ||
| --- | ||
| ## Examples | ||
| ```bash | ||
| # Node.js SEA — default, no extra tools needed beyond node + npx | ||
| webview --build --input src/main.js --name myapp | ||
| # Deno — produces a single binary including the Deno runtime | ||
| webview --build --runtime deno --input src/main.ts --name myapp | ||
| # Bun — fast compilation, includes the Bun runtime | ||
| webview --build --runtime bun --input src/main.ts --name myapp | ||
| # Custom output directory and project name | ||
| webview --build --runtime bun --input src/main.ts --name "MyDesktopApp" --output ./release | ||
| # Dry-run to see what would happen | ||
| webview --build --runtime node --input src/main.js --name myapp --dry-run | ||
| ``` | ||
| --- | ||
| ## Platform notes | ||
| | Platform | Node SEA | Deno compile | Bun compile | | ||
| | -------- | -------------------- | -------------------- | -------------------- | | ||
| | Windows | ✅ `.exe` auto-added | ✅ `.exe` auto-added | ✅ `.exe` auto-added | | ||
| | macOS | ✅ codesign applied | ✅ ad-hoc signed | ✅ | | ||
| | Linux | ✅ | ✅ | ✅ | | ||
| ### Code signing | ||
| - **Node.js** — the CLI removes the existing signature and re-signs with `codesign -s -` (macOS) or `signtool` (Windows) if available. | ||
| - **Deno** — applies an ad-hoc signature on macOS automatically. Use `codesign` / `signtool` for distribution. | ||
| - **Bun** — use `codesign` on macOS after building. On Windows, use `signtool`. | ||
| ### Cross-compilation | ||
| - **Deno**: pass `--target` directly to `deno compile`. Use a custom build script rather than the webviewjs CLI for cross-compile targets. | ||
| - **Bun**: pass `--target` to `bun build`. Same recommendation. | ||
| - **Node SEA**: cross-compilation is not supported; build on the target OS. |
| # Cookies and Storage | ||
| ## Reading cookies | ||
| ```js | ||
| // All cookies for a URL | ||
| const cookies = webview.getCookies('https://example.com'); | ||
| // Every cookie the webview has | ||
| const all = webview.getCookies(); | ||
| for (const c of cookies) { | ||
| console.log(c.name, '=', c.value, ' domain:', c.domain); | ||
| } | ||
| ``` | ||
| `WebviewCookie` fields: | ||
| | Field | Type | Description | | ||
| | ---------- | ------------------------------ | ---------------------- | | ||
| | `name` | `string` | Cookie name | | ||
| | `value` | `string` | Cookie value | | ||
| | `domain` | `string?` | Owning domain | | ||
| | `path` | `string?` | URL path scope | | ||
| | `httpOnly` | `boolean?` | Not accessible from JS | | ||
| | `secure` | `boolean?` | HTTPS-only | | ||
| | `sameSite` | `'strict' \| 'lax' \| 'none'?` | Cross-site policy | | ||
| ## Writing a cookie | ||
| ```js | ||
| webview.setCookie({ | ||
| name: 'session', | ||
| value: 'abc123', | ||
| domain: 'example.com', | ||
| path: '/', | ||
| httpOnly: true, | ||
| secure: true, | ||
| sameSite: 'strict', | ||
| }); | ||
| ``` | ||
| ## Deleting a cookie | ||
| ```js | ||
| // Specific domain + path | ||
| webview.deleteCookie('session', 'example.com', '/'); | ||
| // Name only (removes across all domains/paths) | ||
| webview.deleteCookie('session'); | ||
| ``` | ||
| ## Clearing all browsing data | ||
| Wipes cookies, cache, local storage, IndexedDB, and session data: | ||
| ```js | ||
| webview.clearAllBrowsingData(); | ||
| ``` | ||
| ## Incognito mode | ||
| Pass `incognito: true` when creating the webview to start a session with no persistent storage at all — cookies, cache, and local storage are discarded on exit: | ||
| ```js | ||
| const webview = win.createWebview({ | ||
| url: 'https://example.com', | ||
| incognito: true, | ||
| }); | ||
| ``` |
| # Custom Protocols | ||
| Custom protocols handle URL schemes such as `app://` without starting a local HTTP server. Register each scheme before creating a webview. | ||
| ```js | ||
| import { readFile } from 'node:fs/promises'; | ||
| import { extname, join } from 'node:path'; | ||
| import { Application } from '@webviewjs/webview'; | ||
| const MIME = { | ||
| '.html': 'text/html; charset=utf-8', | ||
| '.js': 'application/javascript; charset=utf-8', | ||
| '.css': 'text/css', | ||
| }; | ||
| const app = new Application(); | ||
| const win = app.createBrowserWindow(); | ||
| win.registerProtocol('app', async (request) => { | ||
| const url = new URL(request.url); | ||
| const path = join(process.cwd(), 'dist', url.pathname); | ||
| try { | ||
| return new Response(await readFile(path), { | ||
| headers: { 'Content-Type': MIME[extname(path)] ?? 'application/octet-stream' }, | ||
| }); | ||
| } catch { | ||
| return new Response(`Not found: ${url.pathname}`, { | ||
| status: 404, | ||
| headers: { 'Content-Type': 'text/plain; charset=utf-8' }, | ||
| }); | ||
| } | ||
| }); | ||
| win.createWebview({ url: 'app://localhost/index.html' }); | ||
| app.run(); | ||
| ``` | ||
| The handler receives the standard global Fetch API `Request`. It may return a | ||
| `Response`, a Promise of a `Response`, or the legacy plain object shown below. | ||
| This makes Fetch-compatible routers such as Hono usable directly. Rejected | ||
| handlers and thrown errors become a `500 text/plain` response. | ||
| ## Hono | ||
| Forward the request directly to a Hono application. Hono returns a standard | ||
| Fetch API `Response`, so no adapter or HTTP server is required: | ||
| ```js | ||
| import { Hono } from 'hono'; | ||
| const router = new Hono(); | ||
| router.get('/*', (context) => { | ||
| return context.html(`<h1>Current page: ${context.req.path}</h1>`); | ||
| }); | ||
| win.registerProtocol('app', (request) => router.fetch(request)); | ||
| win.createWebview({ url: 'app://localhost/' }); | ||
| ``` | ||
| The runnable [Hono custom protocol example](../../examples/custom-protocol-hono.mjs) | ||
| includes dynamic pages, navigation links, pathname rendering, and application | ||
| shutdown handling. | ||
| ## Request and response types | ||
| ```ts | ||
| interface CustomProtocolResponse { | ||
| body: Buffer; | ||
| statusCode?: number; // default: 200 | ||
| mimeType?: string; // default: application/octet-stream | ||
| headers?: HeaderData[]; | ||
| } | ||
| ``` | ||
| ## Security | ||
| Never resolve a request path without checking it remains inside the intended asset directory. Normalize and validate the path before passing it to the file system. The runnable [custom protocol example](../../examples/custom-protocol.mjs) includes this check. | ||
| ## Multiple protocols | ||
| Register multiple schemes before `createWebview()`: | ||
| ```js | ||
| win.registerProtocol('app', appHandler); | ||
| win.registerProtocol('api', async (request) => { | ||
| const response = await fetch(`https://example.test${new URL(request.url).pathname}`); | ||
| return response; | ||
| }); | ||
| ``` | ||
| Protocol registrations are fixed when the webview is created. Registering a scheme after `createWebview()` does not affect an existing webview. | ||
| ## CORS and cache headers | ||
| Use response headers when the page needs them: | ||
| ```js | ||
| return new Response(JSON.stringify(data), { | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| 'Access-Control-Allow-Origin': '*', | ||
| 'Cache-Control': 'no-store', | ||
| }, | ||
| }); | ||
| ``` |
| # IPC Messaging | ||
| wry injects `window.ipc.postMessage()` into each page. Node receives those messages through `webview.onIpcMessage()`. | ||
| ```js | ||
| const webview = win.createWebview({ html: '<button id="ping">Ping</button>' }); | ||
| webview.onIpcMessage((message) => { | ||
| console.log(message.body.toString('utf8')); | ||
| }); | ||
| webview.evaluateScript(` | ||
| document.querySelector('#ping').addEventListener('click', () => { | ||
| window.ipc.postMessage('hello from the page'); | ||
| }); | ||
| `); | ||
| ``` | ||
| ## Custom page global name | ||
| `window.ipc` always remains available. Set `ipcName` to add an alias before page scripts run: | ||
| ```js | ||
| const webview = win.createWebview({ | ||
| url: 'app://localhost/index.html', | ||
| ipcName: 'bindings', | ||
| }); | ||
| ``` | ||
| The page can then call either `window.ipc.postMessage(...)` or `window.bindings.postMessage(...)`. | ||
| On Windows, load IPC-enabled pages through a custom protocol such as `app://`, not a `file:` URL. See [Custom Protocols](./custom-protocols). | ||
| ## Node to page | ||
| Use `evaluateScript()` for one-way messages or DOM updates: | ||
| ```js | ||
| webview.evaluateScript(` | ||
| document.title = 'Connected'; | ||
| `); | ||
| ``` | ||
| Use `evaluateScriptWithCallback()` to receive a serialized JavaScript result: | ||
| ```js | ||
| webview.evaluateScriptWithCallback('document.title', (error, title) => { | ||
| if (error) throw error; | ||
| console.log(title); | ||
| }); | ||
| ``` | ||
| ## JSON messages | ||
| IPC message bodies are bytes. JSON is a practical convention: | ||
| ```js | ||
| // Page | ||
| window.ipc.postMessage(JSON.stringify({ action: 'save', payload: { id: 1 } })); | ||
| // Node | ||
| webview.onIpcMessage((message) => { | ||
| const data = JSON.parse(message.body.toString('utf8')); | ||
| console.log(data.action); | ||
| }); | ||
| ``` | ||
| For a structured Promise-based Node bridge, use [`webview.expose()`](../api/webview#expose-name-target) instead of building your own IPC request/response protocol. |
| # Menus | ||
| ## Basic setup | ||
| ```js | ||
| import { Application } from '@webviewjs/webview'; | ||
| const app = new Application(); | ||
| app.setMenu({ | ||
| items: [ | ||
| { | ||
| label: 'File', | ||
| submenu: { | ||
| items: [ | ||
| { id: 'new', label: 'New', accelerator: 'CmdOrCtrl+N' }, | ||
| { id: 'open', label: 'Open', accelerator: 'CmdOrCtrl+O' }, | ||
| { role: 'separator' }, | ||
| { role: 'quit' }, | ||
| ], | ||
| }, | ||
| }, | ||
| { | ||
| label: 'Edit', | ||
| submenu: { | ||
| items: [ | ||
| { role: 'undo' }, | ||
| { role: 'redo' }, | ||
| { role: 'separator' }, | ||
| { role: 'cut' }, | ||
| { role: 'copy' }, | ||
| { role: 'paste' }, | ||
| ], | ||
| }, | ||
| }, | ||
| ], | ||
| }); | ||
| app.on('custom-menu-click', ({ customMenuEvent }) => { | ||
| switch (customMenuEvent.id) { | ||
| case 'new': | ||
| createNewWindow(); | ||
| break; | ||
| case 'open': | ||
| openFilePicker(); | ||
| break; | ||
| } | ||
| }); | ||
| ``` | ||
| ## Updating the menu at runtime | ||
| ```js | ||
| // Replace the whole menu | ||
| app.setMenu({ items: updatedItems }); | ||
| // Remove entirely | ||
| app.setMenu(null); | ||
| ``` | ||
| ## Per-window menus | ||
| Windows, child popups, and dialogs can have their own distinct menus: | ||
| ```js | ||
| const win = app.createBrowserWindow({ title: 'Editor' }); | ||
| win.setMenu({ | ||
| items: [{ label: 'Editor', submenu: { items: [{ id: 'editor-prefs', label: 'Preferences' }] } }], | ||
| }); | ||
| ``` | ||
| Per-window menus override the global menu for that window. Clicking items emits | ||
| `custom-menu-click` on the application. | ||
| ## Nested submenus | ||
| ```js | ||
| { | ||
| label: 'View', | ||
| submenu: { | ||
| items: [ | ||
| { | ||
| label: 'Zoom', | ||
| submenu: { | ||
| items: [ | ||
| { id: 'zoom-in', label: 'Zoom In', accelerator: 'CmdOrCtrl+=' }, | ||
| { id: 'zoom-out', label: 'Zoom Out', accelerator: 'CmdOrCtrl+-' }, | ||
| { id: 'zoom-reset', label: 'Reset', accelerator: 'CmdOrCtrl+0' }, | ||
| ], | ||
| }, | ||
| }, | ||
| { role: 'fullscreen' }, | ||
| ], | ||
| }, | ||
| } | ||
| ``` | ||
| ## Accelerator reference | ||
| ``` | ||
| CmdOrCtrl+S → Cmd+S on macOS, Ctrl+S elsewhere | ||
| Alt+F4 → literal Alt+F4 | ||
| Shift+CmdOrCtrl+Z → redo shortcut | ||
| F5, F11 → function keys | ||
| ``` | ||
| ## Platform differences | ||
| | Feature | Windows | macOS | Linux | | ||
| | ------------------------ | ---------------------------- | ------------------------ | ------------- | | ||
| | Menu bar | Per-window, inside title bar | App-level, top of screen | Not supported | | ||
| | Predefined roles | Most | All | N/A | | ||
| | Accelerators | Yes | Yes | N/A | | ||
| | `CustomMenuClick` events | Yes | Yes | Never fires | |
| # Multiple Windows | ||
| ## Opening several windows | ||
| ```js | ||
| const app = new Application(); | ||
| function createWindow(url) { | ||
| const win = app.createBrowserWindow({ title: url, width: 900, height: 600 }); | ||
| const webview = win.createWebview({ url }); | ||
| return { win, webview }; | ||
| } | ||
| const win1 = createWindow('https://example.com'); | ||
| const win2 = createWindow('https://nodejs.org'); | ||
| app.run(); | ||
| ``` | ||
| Both windows share the same event loop driven by the single `setInterval` pump. | ||
| ## Tracking windows yourself | ||
| ```js | ||
| const windows = new Map(); // id → { win, webview } | ||
| function openWindow(id, url) { | ||
| if (windows.has(id)) { | ||
| windows.get(id).win.show(); | ||
| return; | ||
| } | ||
| const win = app.createBrowserWindow({ title: id }); | ||
| const webview = win.createWebview({ url }); | ||
| windows.set(id, { win, webview }); | ||
| } | ||
| app.on('window-close-requested', () => { | ||
| // The window has been hidden by the runtime. | ||
| }); | ||
| app.on('application-close-requested', () => { | ||
| app.exit(); | ||
| }); | ||
| ``` | ||
| ## Child (popup) windows | ||
| Child windows are positioned relative to a parent window and are useful for dialogs, palettes, and tool panels. | ||
| ```js | ||
| const child = app.createChildBrowserWindow({ | ||
| title: 'Settings', | ||
| width: 400, | ||
| height: 300, | ||
| }); | ||
| const childWebview = child.createWebview({ | ||
| url: 'app://settings', | ||
| // x/y/width/height position the webview within the child window | ||
| }); | ||
| ``` | ||
| ## Showing / hiding instead of closing | ||
| The runtime hides a window (rather than destroying it) when the user clicks the OS close button. You can reuse it: | ||
| ```js | ||
| function toggleWindow(win) { | ||
| win.setVisible(!win.isVisible()); // or win.show() / win.hide() | ||
| } | ||
| ``` | ||
| ## Window lifecycle events | ||
| ```js | ||
| app.on('window-close-requested', () => { | ||
| // One window was hidden. | ||
| }); | ||
| app.on('application-close-requested', () => { | ||
| // All tracked windows are now hidden. | ||
| app.exit(); | ||
| }); | ||
| ``` |
| # Android | ||
| `androidContentRect()` returns the current native content inset rectangle. | ||
| `androidConfig()` returns the current Android configuration as a diagnostic | ||
| string. | ||
| System tray creation is unsupported and returns an error on Android. | ||
| See [BrowserWindow platform APIs](../api/browser-window#android-runtime-extensions). |
| # iOS | ||
| `BrowserWindowOptions` supports initial content scale, valid orientations, | ||
| home-indicator visibility, deferred system-gesture edges, status-bar | ||
| visibility. | ||
| The same settings can be changed at runtime through Tao's iOS window | ||
| extensions. | ||
| Screen-edge values are a bitmask: top `1`, left `2`, bottom `4`, and right | ||
| `8`. | ||
| See [BrowserWindow platform APIs](../api/browser-window#ios-options-and-runtime-extensions). |
| # Linux | ||
| ## X11 and Wayland window attributes | ||
| X11 creation options expose visual and screen IDs, `WM_CLASS` names, | ||
| override-redirect, `_NET_WM_WINDOW_TYPE`, and base size. | ||
| Wayland creation options expose the application ID and instance name. | ||
| `getWaylandSurface()` returns Tao's native Wayland surface pointer for | ||
| interoperability, or `0n` when the window uses X11. | ||
| See [BrowserWindow platform APIs](../api/browser-window#linux-creation-options). | ||
| ## WebKitGTK | ||
| WebviewJS on Linux uses **WebKitGTK 4.1** (WebKit-based). Install the runtime and development headers before building or running: | ||
| ```bash | ||
| # Debian / Ubuntu | ||
| sudo apt install libwebkit2gtk-4.1-dev libxdo-dev | ||
| # Fedora / RHEL | ||
| sudo dnf install webkit2gtk4.1-devel libxdo-devel | ||
| # Arch Linux | ||
| sudo pacman -S webkit2gtk-4.1 xdotool | ||
| ``` | ||
| ## Display server support | ||
| WebviewJS supports both **X11** and **Wayland** through Tao. The active backend | ||
| is selected at runtime from the available display environment. | ||
| No `Cargo.toml` changes or feature flags are needed. | ||
| ## Menus | ||
| Native menu bars use Muda's GTK integration. WebviewJS obtains the GTK window | ||
| and default vertical box directly from Tao, so `app.setMenu()`, window menu | ||
| options, and `CustomMenuClick` events work on Linux. | ||
| ## Wayland-specific notes | ||
| - Window decorations on Wayland are drawn client-side (via `adwaita` CSD). | ||
| - Absolute window positioning (`win.setPosition()`) may be ignored on compositors that enforce the XDG Shell placement protocol. | ||
| - Screen capture / content protection APIs are compositor-dependent. | ||
| ## `setSkipTaskbar` | ||
| This calls Tao's GTK implementation. The final behavior remains subject to the | ||
| desktop environment and window manager. | ||
| ## Tested environments | ||
| | Distribution | Display | Status | | ||
| | ------------ | --------------- | --------- | | ||
| | Ubuntu 22.04 | X11 / Wayland | Supported | | ||
| | Fedora 40 | Wayland (GNOME) | Supported | | ||
| | Arch Linux | X11 / Wayland | Supported | | ||
| | Debian 12 | X11 | Supported | |
| # macOS | ||
| ## WebKit | ||
| WebviewJS on macOS uses the built-in **WebKit** (WKWebView). No runtime installation is required. macOS 10.15 Catalina or later is supported. | ||
| ## Main thread requirement | ||
| macOS enforces that all GUI operations happen on the main thread. WebviewJS handles this automatically — do **not** create `Application` or `BrowserWindow` from a `worker_threads` Worker or any async context that moves the call off the main thread. | ||
| ## App-level menu bar | ||
| On macOS the menu bar spans the top of the entire screen and belongs to the application, not any individual window. | ||
| ```js | ||
| app.setMenu({ | ||
| items: [ | ||
| /* … */ | ||
| ], | ||
| }); | ||
| ``` | ||
| `init_for_nsapp()` is called automatically when `Application` is created. | ||
| ## Standard macOS roles | ||
| All predefined roles work on macOS, including: | ||
| | Role | Keyboard shortcut | | ||
| | ----------------- | ----------------- | | ||
| | `hide` | ⌘H | | ||
| | `hideothers` | ⌥⌘H | | ||
| | `showall` | — | | ||
| | `bringalltofront` | — | | ||
| | `services` | Services submenu | | ||
| | `quit` | ⌘Q | | ||
| | `about` | — | | ||
| ## Fullscreen | ||
| ```js | ||
| win.setFullscreen(FullscreenType.Borderless); // uses native macOS fullscreen | ||
| ``` | ||
| ## Transparent window | ||
| ```js | ||
| const win = app.createBrowserWindow({ transparent: true, decorations: false }); | ||
| ``` | ||
| Combine with `webview.setBackgroundColor(0, 0, 0, 0)` for a fully transparent, frameless window. | ||
| ## Platform extensions | ||
| `BrowserWindowOptions` exposes native titlebar, full-size content, shadow, | ||
| first-mouse, HiDPI, tabbing, Option-as-Alt, and borderless-game attributes. | ||
| Runtime methods support simple fullscreen, shadows, native window tabs, | ||
| document-edited state, Option-as-Alt behavior, and borderless-game mode. | ||
| See [BrowserWindow platform APIs](../api/browser-window#macos-creation-options). | ||
| ## Known limitations | ||
| - **`setSkipTaskbar`** is a no-op on macOS (use `NSApplication.setActivationPolicy` for dock hiding, which requires an entitlement). | ||
| - **Click-through** (`setIgnoreCursorEvents`) is supported via `NSWindow.setIgnoresMouseEvents`. | ||
| - **Window icons** are not shown on the macOS dock — the app icon is set via the bundle's `Info.plist`. |
| # Windows | ||
| ## WebView2 runtime | ||
| WebviewJS on Windows uses the **WebView2** engine (Chromium-based). | ||
| - **Windows 11**: WebView2 runtime ships pre-installed. | ||
| - **Windows 10**: The runtime is auto-downloaded and installed the first time it is needed. You can also pre-install it via the [Evergreen bootstrapper](https://developer.microsoft.com/en-us/microsoft-edge/webview2/). | ||
| Check the installed version at runtime: | ||
| ```js | ||
| import { getWebviewVersion } from '@webviewjs/webview'; | ||
| console.log(getWebviewVersion()); // e.g. "128.0.2739.42" | ||
| ``` | ||
| ## Menu bar | ||
| The menu bar is attached to each window's title bar (standard Win32 behaviour). Each window can have its own menu or share the global one set via `app.setMenu()`. | ||
| ## DPI / HiDPI | ||
| Tao reports the monitor's scale factor and scales the window accordingly. Use | ||
| `win.scaleFactor()` to get the current DPI ratio, and logical pixels when | ||
| positioning child elements. | ||
| ## Taskbar integration | ||
| ```js | ||
| // Hide from taskbar (e.g. for system-tray apps) | ||
| win.setSkipTaskbar(true); | ||
| // Progress ring in the taskbar icon | ||
| win.setProgressBar({ state: ProgressBarState.Normal, progress: 42 }); | ||
| ``` | ||
| WebviewJS exposes Tao's Windows window extensions for taskbar icons, | ||
| skip-taskbar state, undecorated shadows, enabled state, and native handles. See | ||
| [Windows extensions](../api/browser-window#windows-extensions). | ||
| ## Content protection | ||
| Prevents the window contents from appearing in screenshots or screen-recording APIs: | ||
| ```js | ||
| win.setContentProtection(true); | ||
| ``` | ||
| ## Known limitations | ||
| - In wry 0.53, a `file:` page that calls `window.ipc.postMessage()` can abort because WebView2 reports the page source as a `file:` URI and wry attempts to convert it to an HTTP request URI. Use an `app://` custom protocol for pages that use IPC or `webview.expose()`. | ||
| - `setIgnoreCursorEvents(true)` (click-through) is supported on Windows via the `WS_EX_TRANSPARENT` extended window style. | ||
| - Window transparency requires `transparent: true` at creation time; it cannot be toggled at runtime. | ||
| - Tao does not expose a window blur or unfocus API. Keyboard focus can be given | ||
| to the webview via `webview.focus()`. |
| # `@webviewjs/webview` | ||
|  | ||
| Robust cross-platform webview library for Node.js written in Rust. It is a native binding to [wry](https://github.com/tauri-apps/wry) and [wry](https://github.com/tauri-apps/wry) allowing you to create native desktop windows from JavaScript and TypeScript. | ||
| ## Highlights | ||
| - Promise-based application readiness with optional automatic event pumping. | ||
| - Non-blocking application pumping, so ordinary Node timers and I/O continue running. | ||
| - Browser windows, menus, dialogs, cookies, DevTools, and window controls. | ||
| - Typed EventEmitter APIs for applications, windows, webviews, and system tray icons. | ||
| - Shared browser contexts for profile, cookie, cache, and storage isolation. | ||
| - Cross-platform system tray icons with menus and runtime updates. | ||
| - Native Windows, macOS, X11, Wayland, iOS, and Android window extensions. | ||
| - IPC through `window.ipc.postMessage()`, with an optional alias such as `window.bindings`. | ||
| - Fetch-compatible asynchronous custom protocols, including Hono routing without an HTTP server. | ||
| - Promise-based `webview.expose()` namespaces for page-to-Node calls. | ||
|  | ||
| > [!CAUTION] | ||
| > This library is still in development and not ready for production use. Feel free to experiment with it and report any issues you find. | ||
| See the [full documentation](./) for API references, guides, platform | ||
| notes, and runnable examples. | ||
| # Documentation | ||
| ## Getting started | ||
| | | | | ||
| | ---------------------------------------------- | ------------------------------- | | ||
| | [Installation](./getting-started/installation) | System requirements and setup | | ||
| | [Quick Start](./getting-started/quick-start) | Your first window in minutes | | ||
| | [Event Loop](./getting-started/event-loop) | How the non-blocking pump works | | ||
| ## API reference | ||
| | | | | ||
| | ------------------------------------- | ------------------------------------------------------ | | ||
| | [Application](./api/application) | Root object — event loop, windows, menus | | ||
| | [BrowserWindow](./api/browser-window) | OS window, size, position, cursor, decorations | | ||
| | [Webview](./api/webview) | Embedded browser — navigation, cookies, script, bounds | | ||
| | [WebContext](./api/web-context) | Shared browser data, profiles, and automation | | ||
| | [System Tray](./api/tray) | Tray icons, menus, updates, and pointer events | | ||
| | [Menu](./api/menu) | Native menu bar construction | | ||
| | [Types](./api/types) | Shared interfaces and enums | | ||
| ## Guides | ||
| | | | | ||
| | ----------------------------------------------------- | ----------------------------------------------- | | ||
| | [Building Executables](./guides/building-executables) | Compile to `.exe` / binary with node, deno, bun | | ||
| | [IPC Messaging](./guides/ipc-messaging) | Page ↔ Node communication | | ||
| | [Menus](./guides/menus) | Building menu bars with roles and accelerators | | ||
| | [Multiple Windows](./guides/multiple-windows) | Managing several windows | | ||
| | [Cookies & Storage](./guides/cookies-and-storage) | Reading, writing, and clearing cookies | | ||
| | [Custom Protocols](./guides/custom-protocols) | Serving local content to the webview | | ||
| ## Platform notes | ||
| | | | | ||
| | ----------------------------- | ----------------------------------------- | | ||
| | [Windows](./platform/windows) | WebView2, taskbar, DPI | | ||
| | [macOS](./platform/macos) | WebKit, main-thread requirement, app menu | | ||
| | [Linux](./platform/linux) | WebKitGTK, Wayland/X11, menu limitations | | ||
| | [iOS](./platform/ios) | Orientation, status bar, and gestures | | ||
| | [Android](./platform/android) | Content rectangle and configuration | | ||
| # Installation | ||
| ```bash | ||
| npm install @webviewjs/webview | ||
| ``` | ||
| # Supported platforms | ||
| | Platform | OS | Arch | Supported | | ||
| | ----------------------------- | ------- | ----- | ----------------- | | ||
| | x86_64-pc-windows-msvc | Windows | x64 | ✅ | | ||
| | i686-pc-windows-msvc | Windows | x86 | ✅ | | ||
| | aarch64-pc-windows-msvc | Windows | arm64 | ✅ | | ||
| | x86_64-apple-darwin | macOS | x64 | ✅ | | ||
| | aarch64-apple-darwin | macOS | arm64 | ✅ | | ||
| | x86_64-unknown-linux-gnu | Linux | x64 | ✅ | | ||
| | aarch64-unknown-linux-gnu | Linux | arm64 | ✅ | | ||
| | armv7-unknown-linux-gnueabihf | Linux | armv7 | ✅ | | ||
| | i686-unknown-linux-gnu | Linux | x86 | ⚠️ (no CI) | | ||
| | aarch64-linux-android | Android | arm64 | ⚠️ (experimental) | | ||
| | armv7-linux-androideabi | Android | armv7 | ⚠️ (experimental) | | ||
| | x86_64-unknown-freebsd | FreeBSD | x64 | ⚠️ (no CI) | | ||
| # Examples | ||
| ## Load external url | ||
| ```js | ||
| import { Application } from '@webviewjs/webview'; | ||
| // or | ||
| const { Application } = require('@webviewjs/webview'); | ||
| const app = new Application(); | ||
| let mainWindow = null; | ||
| let mainWebview = null; | ||
| app.whenReady().then(() => { | ||
| mainWindow = app.createBrowserWindow(); | ||
| mainWebview = mainWindow.createWebview({ url: 'https://nodejs.org' }); | ||
| }); | ||
| ``` | ||
| ## Event pumping | ||
| `app.whenReady()` starts the non-blocking event pump by default: | ||
| ```js | ||
| await app.whenReady({ interval: 16, ref: true }); | ||
| ``` | ||
| For manual startup, disable auto-run: | ||
| ```js | ||
| const ready = app.whenReady({ autoRun: false }); | ||
| app.run({ interval: 16, ref: true }); | ||
| await ready; | ||
| ``` | ||
| `interval` defaults to `16` milliseconds and `ref` defaults to `true`. Use `app.pumpEvents()` for manual pumping. | ||
| ## System tray | ||
| Keep a strong JavaScript reference when you need to call tray methods or keep | ||
| its listeners reachable: | ||
| ```js | ||
| let tray = null; | ||
| app.whenReady().then(() => { | ||
| tray = app.createTrayIcon({ | ||
| id: 'main', | ||
| icon: { data: rgba, width: 16, height: 16 }, | ||
| tooltip: 'My application', | ||
| menu: { items: [{ id: 'quit', label: 'Quit' }] }, | ||
| }); | ||
| tray.on('click', (event) => console.log(event)); | ||
| }); | ||
| ``` | ||
| See the [system tray reference](./api/tray) and | ||
| [runnable tray example](../examples/tray.mjs). | ||
| ## IPC and exposed functions | ||
| The webview page can send messages to Node through `window.ipc.postMessage()`: | ||
| ```js | ||
| const webview = window.createWebview({ ipcName: 'bindings' }); | ||
| webview.onIpcMessage((message) => console.log(message.body.toString())); | ||
| ``` | ||
| `ipcName` adds an alias, so the page can use `window.bindings.postMessage(...)`; `window.ipc` remains available. | ||
| For typed request/response style calls, expose a namespace: | ||
| ```js | ||
| webview.expose('native', { | ||
| version: '0.1.4', | ||
| readConfig: async () => JSON.parse(await readFile('./config.json', 'utf8')), | ||
| }); | ||
| ``` | ||
| In the page: | ||
| ```js | ||
| console.log(window.native.version); | ||
| const config = await window.native.readConfig(); | ||
| ``` | ||
| Every exposed function returns a Promise in the page. Values, arguments, and results must be JSON-serializable. Violations use `SerializationError`. | ||
| ## Asynchronous custom protocols | ||
| Register a protocol before creating its webview: | ||
| ```js | ||
| window.registerProtocol('app', async (request) => { | ||
| const filePath = join(process.cwd(), 'dist', new URL(request.url).pathname); | ||
| try { | ||
| return new Response(await readFile(filePath), { | ||
| headers: { 'Content-Type': 'text/html; charset=utf-8' }, | ||
| }); | ||
| } catch { | ||
| return new Response('Not found', { | ||
| status: 404, | ||
| headers: { 'Content-Type': 'text/plain; charset=utf-8' }, | ||
| }); | ||
| } | ||
| }); | ||
| window.createWebview({ url: 'app://localhost/index.html' }); | ||
| ``` | ||
| See [Custom Protocols](./guides/custom-protocols), [IPC](./guides/ipc-messaging), and the runnable [custom protocol](../examples/custom-protocol.mjs) and [expose](../examples/expose.mjs) examples for more details. | ||
| ## Menu System | ||
| WebviewJS provides a cross-platform menu system that works on macOS, Windows, and Linux. | ||
| ### Basic Menu Setup | ||
| ```js | ||
| import { Application } from '@webviewjs/webview'; | ||
| const app = new Application(); | ||
| // Set global application menu | ||
| app.setMenu({ | ||
| items: [ | ||
| { | ||
| label: 'File', | ||
| submenu: { | ||
| items: [ | ||
| { id: 'new', label: 'New', accelerator: 'CmdOrCtrl+N' }, | ||
| { id: 'open', label: 'Open', accelerator: 'CmdOrCtrl+O' }, | ||
| { role: 'separator' }, | ||
| { id: 'quit', label: 'Quit', accelerator: 'CmdOrCtrl+Q' }, | ||
| ], | ||
| }, | ||
| }, | ||
| { | ||
| label: 'Edit', | ||
| submenu: { | ||
| items: [{ role: 'copy' }, { role: 'paste' }, { role: 'cut' }, { role: 'selectall' }], | ||
| }, | ||
| }, | ||
| ], | ||
| }); | ||
| const window = app.createBrowserWindow(); | ||
| const webview = window.createWebview({ url: 'https://nodejs.org' }); | ||
| app.run(); | ||
| ``` | ||
| ### Menu Event Handling | ||
| ```js | ||
| import { Application } from '@webviewjs/webview'; | ||
| const app = new Application(); | ||
| // Handle menu events | ||
| app.on('custom-menu-click', ({ customMenuEvent: menuEvent }) => { | ||
| console.log(`Menu item clicked: ${menuEvent.id}`); | ||
| console.log(`From window: ${menuEvent.windowId}`); | ||
| // Handle specific menu items | ||
| switch (menuEvent.id) { | ||
| case 'new': | ||
| console.log('Creating new document...'); | ||
| break; | ||
| case 'open': | ||
| console.log('Opening file...'); | ||
| break; | ||
| case 'quit': | ||
| app.exit(); | ||
| break; | ||
| } | ||
| }); | ||
| // Set up menu... | ||
| app.setMenu({ | ||
| /* ... */ | ||
| }); | ||
| ``` | ||
| ### Window-Specific Menus | ||
| ```js | ||
| const app = new Application(); | ||
| // Create window with custom menu | ||
| const window = app.createBrowserWindow({ | ||
| title: 'Custom Window', | ||
| menu: { | ||
| items: [ | ||
| { | ||
| id: 'window-action', | ||
| label: 'Window Action', | ||
| accelerator: 'Ctrl+W', | ||
| }, | ||
| ], | ||
| }, | ||
| }); | ||
| // Or check if window has a menu | ||
| if (window.hasMenu()) { | ||
| console.log('This window has a menu'); | ||
| } | ||
| ``` | ||
| ### Menu Item Options | ||
| - **`id`**: Unique identifier for the menu item (used in events) | ||
| - **`label`**: Display text for the menu item | ||
| - **`enabled`**: Whether the item is clickable (default: true) | ||
| - **`accelerator`**: Keyboard shortcut (e.g., "CmdOrCtrl+N", "Alt+F4") | ||
| - **`submenu`**: Nested menu items | ||
| - **`role`**: Predefined menu items with built-in behavior | ||
| ### Predefined Menu Roles | ||
| - **`"copy"`**: Standard copy action | ||
| - **`"paste"`**: Standard paste action | ||
| - **`"cut"`**: Standard cut action | ||
| - **`"selectall"`**: Select all text action | ||
| - **`"separator"`**: Visual separator line | ||
| ## IPC | ||
| ```js | ||
| const app = new Application(); | ||
| const window = app.createBrowserWindow(); | ||
| const webview = window.createWebview({ | ||
| html: `<!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>Webview</title> | ||
| </head> | ||
| <body> | ||
| <h1 id="output">Hello world!</h1> | ||
| <button id="btn">Click me!</button> | ||
| <script> | ||
| btn.onclick = function send() { | ||
| window.ipc.postMessage('Hello from webview'); | ||
| } | ||
| </script> | ||
| </body> | ||
| </html> | ||
| `, | ||
| preload: `window.onIpcMessage = function(data) { | ||
| const output = document.getElementById('output'); | ||
| output.innerText = \`Server Sent A Message: \${data}\`; | ||
| }`, | ||
| }); | ||
| if (!webview.isDevtoolsOpen()) webview.openDevtools(); | ||
| webview.onIpcMessage((data) => { | ||
| const reply = `You sent ${data.body.toString('utf-8')}`; | ||
| webview.evaluateScript(`onIpcMessage("${reply}")`); | ||
| }); | ||
| app.run(); | ||
| ``` | ||
| ## Closing the Application | ||
| You can close the application, windows, and webviews gracefully to ensure all resources (including temporary folders) are cleaned up properly. | ||
| ```js | ||
| const app = new Application(); | ||
| const window = app.createBrowserWindow(); | ||
| const webview = window.createWebview({ url: 'https://nodejs.org' }); | ||
| app.on('application-close-requested', () => { | ||
| console.log('Application is closing, cleaning up resources...'); | ||
| }); | ||
| app.on('window-close-requested', () => { | ||
| console.log('Window close requested'); | ||
| }); | ||
| // Close the application gracefully (cleans up temp folders) | ||
| app.exit(); | ||
| // Or hide/show the window | ||
| window.hide(); // Hide the window | ||
| window.show(); // Show the window again | ||
| // Or reload the webview | ||
| webview.reload(); | ||
| ``` | ||
| For more details on closing applications and cleaning up resources, see the [Closing Guide](./CLOSING_GUIDE). | ||
| ## Keep strong references | ||
| Retain `BrowserWindow`, `Webview`, `WebContext`, and `TrayIcon` wrappers for as | ||
| long as you need to call their methods or retain their JavaScript listeners. | ||
| Avoid discarded temporary handles: | ||
| ```js | ||
| const windows = []; | ||
| app.whenReady().then(() => { | ||
| const window = app.createBrowserWindow(); | ||
| const webview = window.createWebview({ url: 'https://example.com' }); | ||
| windows.push({ window, webview }); | ||
| }); | ||
| ``` | ||
| The root `Application` owns native resources created through it. `app.exit()`, | ||
| `app[Symbol.dispose]()`, and application garbage collection dispose those | ||
| resources in shutdown order. Retained wrappers then report `isDisposed() === | ||
| true`, and method calls fail with a disposed error. Individual windows, | ||
| webviews, contexts, and tray icons also support `dispose()` and | ||
| `Symbol.dispose`. | ||
| Check out [examples](../examples) directory for more examples: | ||
| - **[menu-system.mjs](../examples/menu-system.mjs)** - Comprehensive menu system demonstration with all features | ||
| - **[window-menus.mjs](../examples/window-menus.mjs)** - Window-specific vs global menu examples | ||
| - **[http/](../examples/http/)** - Serving content from a web server to webview | ||
| - **[transparent.mjs](../examples/transparent.mjs)** - Transparent window example | ||
| - **[close-example.mjs](../examples/close-example.mjs)** - Graceful application closing | ||
| Run any example with: `node examples/menu-system.mjs` (after building the project) | ||
| # Building executables | ||
| > [!WARNING] | ||
| > The CLI feature is very experimental and may not work as expected. Please report any issues you find. | ||
| The `webview` CLI compiles your app into a single self-contained executable. The runtime is auto-detected (`Bun` → bun, `Deno` → deno, otherwise Node.js), or you can override it: | ||
| ```bash | ||
| # Auto-detected runtime | ||
| webview --build --input ./path/to/your/script.js --output ./dist --name my-app | ||
| # Explicit runtime | ||
| webview --build --runtime node --input ./src/index.js --name my-app | ||
| webview --build --runtime deno --input ./src/index.ts --name my-app | ||
| webview --build --runtime bun --input ./src/index.ts --name my-app | ||
| ``` | ||
| | Flag | Default | Description | | ||
| | -------------------- | ------------- | -------------------------- | | ||
| | `--runtime` / `-R` | auto-detected | `node`, `deno`, or `bun` | | ||
| | `--input` / `-i` | `./index.js` | Entry file | | ||
| | `--output` / `-o` | `./dist` | Output directory | | ||
| | `--name` / `-n` | `webviewjs` | Executable name | | ||
| | `--resources` / `-r` | — | JSON asset map (node only) | | ||
| For the full compilation guide including cross-compilation and code signing, see [Building Executables](./guides/building-executables). | ||
| # Development | ||
| ## Prerequisites | ||
| - [Bun](https://bun.sh/) >= 1.3.0 | ||
| - [Rust](https://www.rust-lang.org/) stable toolchain | ||
| - [Node.js](https://nodejs.org/) >= 24 (for testing) | ||
| ## Setup | ||
| ```bash | ||
| bun install | ||
| ``` | ||
| ## Build | ||
| ```bash | ||
| bun run build | ||
| ``` |
| export * from './js-bindings'; | ||
| export class SerializationError extends Error { | ||
| name: 'SerializationError'; | ||
| } | ||
| export type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }; | ||
| export type ExposedTarget = Record<string, JsonValue | ((...args: any[]) => unknown | Promise<unknown>)>; | ||
| export type NotificationPermission = 'granted'; | ||
| export type NotificationDirection = 'auto' | 'ltr' | 'rtl'; | ||
| export type NotificationEventName = 'click' | 'close' | 'error' | 'show'; | ||
| export interface NotificationAction { | ||
| action: string; | ||
| title: string; | ||
| icon?: string; | ||
| } | ||
| export interface NotificationOptions { | ||
| body?: string; | ||
| icon?: string; | ||
| image?: string | Buffer; | ||
| badge?: string; | ||
| tag?: string; | ||
| data?: unknown; | ||
| dir?: NotificationDirection; | ||
| lang?: string; | ||
| renotify?: boolean; | ||
| requireInteraction?: boolean; | ||
| persistent?: boolean; | ||
| actions?: NotificationAction[]; | ||
| silent?: boolean; | ||
| timestamp?: number; | ||
| vibrate?: number | number[]; | ||
| } | ||
| export interface NotificationEvent { | ||
| type: NotificationEventName; | ||
| target: Notification; | ||
| action?: string; | ||
| error?: Error; | ||
| } | ||
| export class Notification { | ||
| constructor(title: string, options?: NotificationOptions); | ||
| static readonly permission: NotificationPermission; | ||
| static requestPermission(): Promise<NotificationPermission>; | ||
| readonly title: string; | ||
| readonly body: string; | ||
| readonly icon: string; | ||
| readonly image: string | Buffer; | ||
| readonly badge: string; | ||
| readonly tag: string; | ||
| readonly data: unknown; | ||
| readonly dir: NotificationDirection; | ||
| readonly lang: string; | ||
| readonly renotify: boolean; | ||
| readonly requireInteraction: boolean; | ||
| readonly persistent: boolean; | ||
| readonly actions: NotificationAction[]; | ||
| readonly silent: boolean; | ||
| readonly timestamp: number; | ||
| readonly vibrate: number | number[]; | ||
| onclick: ((event: NotificationEvent) => void) | null; | ||
| onclose: ((event: NotificationEvent) => void) | null; | ||
| onerror: ((event: NotificationEvent) => void) | null; | ||
| onshow: ((event: NotificationEvent) => void) | null; | ||
| close(): void; | ||
| on(event: NotificationEventName, listener: (event: NotificationEvent) => void): this; | ||
| once(event: NotificationEventName, listener: (event: NotificationEvent) => void): this; | ||
| off(event: NotificationEventName, listener: (event: NotificationEvent) => void): this; | ||
| addListener(event: NotificationEventName, listener: (event: NotificationEvent) => void): this; | ||
| removeListener(event: NotificationEventName, listener: (event: NotificationEvent) => void): this; | ||
| removeAllListeners(event?: NotificationEventName): this; | ||
| listenerCount(event: NotificationEventName, listener?: (event: NotificationEvent) => void): number; | ||
| listeners(event: NotificationEventName): Function[]; | ||
| rawListeners(event: NotificationEventName): Function[]; | ||
| emit(event: NotificationEventName, eventObject: NotificationEvent): boolean; | ||
| eventNames(): Array<string | symbol>; | ||
| } | ||
| export interface ApplicationEventMap { | ||
| 'window-close-requested': import('./js-bindings').ApplicationEvent; | ||
| 'application-close-requested': import('./js-bindings').ApplicationEvent; | ||
| 'custom-menu-click': import('./js-bindings').ApplicationEvent; | ||
| ready: import('./js-bindings').ApplicationEvent; | ||
| } | ||
| export interface TrayEventMap { | ||
| click: import('./js-bindings').TrayEventPayload; | ||
| 'double-click': import('./js-bindings').TrayEventPayload; | ||
| enter: import('./js-bindings').TrayEventPayload; | ||
| move: import('./js-bindings').TrayEventPayload; | ||
| leave: import('./js-bindings').TrayEventPayload; | ||
| } | ||
| export type ApplicationWhenReadyOptions = | ||
| | { | ||
| autoRun?: true; | ||
| interval?: number; | ||
| ref?: boolean; | ||
| } | ||
| | { | ||
| autoRun: false; | ||
| interval?: never; | ||
| ref?: never; | ||
| }; | ||
| // ── Webview events ──────────────────────────────────────────────────────────── | ||
| export interface WebviewPageLoadEvent { | ||
| event: number; | ||
| url?: string; | ||
| } | ||
| export interface WebviewTitleChangedEvent { | ||
| event: number; | ||
| title?: string; | ||
| } | ||
| export interface WebviewDownloadEvent { | ||
| event: number; | ||
| url?: string; | ||
| /** Only set for `download-completed` events. */ | ||
| success?: boolean; | ||
| } | ||
| export interface WebviewNavigationEvent { | ||
| event: number; | ||
| url?: string; | ||
| } | ||
| export interface WebviewNewWindowEvent { | ||
| event: number; | ||
| url?: string; | ||
| } | ||
| /** Maps Webview event names to their typed payloads. */ | ||
| export interface WebviewEventMap { | ||
| 'page-load-started': WebviewPageLoadEvent; | ||
| 'page-load-finished': WebviewPageLoadEvent; | ||
| 'title-changed': WebviewTitleChangedEvent; | ||
| 'download-started': WebviewDownloadEvent; | ||
| 'download-completed': WebviewDownloadEvent; | ||
| /** Fired for every navigation attempt. Use `navigationHandler` option to allow/deny. */ | ||
| navigation: WebviewNavigationEvent; | ||
| /** Fired when the page attempts to open a new browser window (`window.open`, `target="_blank"`, etc.). */ | ||
| 'new-window': WebviewNewWindowEvent; | ||
| } | ||
| export interface WindowMoveEvent { | ||
| event: number; | ||
| x: number; | ||
| y: number; | ||
| } | ||
| export interface WindowResizeEvent { | ||
| event: number; | ||
| width: number; | ||
| height: number; | ||
| } | ||
| export interface WindowMouseEvent { | ||
| event: number; | ||
| x: number; | ||
| y: number; | ||
| button?: number; | ||
| modifiers?: number; | ||
| } | ||
| export interface WindowScrollEvent { | ||
| event: number; | ||
| deltaX: number; | ||
| deltaY: number; | ||
| } | ||
| export interface WindowBaseEvent { | ||
| event: number; | ||
| } | ||
| export interface WindowKeyEvent { | ||
| event: number; | ||
| key?: string; | ||
| code?: string; | ||
| modifiers?: number; | ||
| isRepeat?: boolean; | ||
| } | ||
| export interface WindowFileEvent { | ||
| event: number; | ||
| files?: string[]; | ||
| } | ||
| export interface WindowScaleEvent { | ||
| event: number; | ||
| scaleFactor: number; | ||
| } | ||
| export interface WindowThemeEvent { | ||
| event: number; | ||
| text: 'light' | 'dark'; | ||
| } | ||
| export interface WindowImeEvent { | ||
| event: number; | ||
| text?: string; | ||
| phase: 'enabled' | 'preedit' | 'commit' | 'disabled'; | ||
| } | ||
| export interface WindowTouchEvent { | ||
| event: number; | ||
| x: number; | ||
| y: number; | ||
| touchId: number; | ||
| phase: 'started' | 'moved' | 'ended' | 'cancelled'; | ||
| } | ||
| export interface BrowserWindowEventMap { | ||
| move: WindowMoveEvent; | ||
| resize: WindowResizeEvent; | ||
| close: WindowBaseEvent; | ||
| focus: WindowBaseEvent; | ||
| blur: WindowBaseEvent; | ||
| 'mouse-enter': WindowMouseEvent; | ||
| 'mouse-leave': WindowBaseEvent; | ||
| 'mouse-move': WindowMouseEvent; | ||
| 'mouse-down': WindowMouseEvent; | ||
| 'mouse-up': WindowMouseEvent; | ||
| scroll: WindowScrollEvent; | ||
| 'key-down': WindowKeyEvent; | ||
| 'key-up': WindowKeyEvent; | ||
| 'file-drop': WindowFileEvent; | ||
| 'file-hover': WindowFileEvent; | ||
| 'file-hover-cancelled': WindowBaseEvent; | ||
| 'scale-factor-changed': WindowScaleEvent; | ||
| 'theme-changed': WindowThemeEvent; | ||
| ime: WindowImeEvent; | ||
| touch: WindowTouchEvent; | ||
| } | ||
| declare module './js-bindings' { | ||
| interface TrayIcon { | ||
| [Symbol.dispose](): void; | ||
| on<K extends keyof TrayEventMap>(event: K, listener: (payload: TrayEventMap[K]) => void): this; | ||
| on(event: string, listener: (...args: any[]) => void): this; | ||
| once<K extends keyof TrayEventMap>(event: K, listener: (payload: TrayEventMap[K]) => void): this; | ||
| once(event: string, listener: (...args: any[]) => void): this; | ||
| off<K extends keyof TrayEventMap>(event: K, listener: (payload: TrayEventMap[K]) => void): this; | ||
| off(event: string, listener: (...args: any[]) => void): this; | ||
| addListener<K extends keyof TrayEventMap>(event: K, listener: (payload: TrayEventMap[K]) => void): this; | ||
| addListener(event: string, listener: (...args: any[]) => void): this; | ||
| removeListener<K extends keyof TrayEventMap>(event: K, listener: (payload: TrayEventMap[K]) => void): this; | ||
| removeListener(event: string, listener: (...args: any[]) => void): this; | ||
| removeAllListeners(event?: string): this; | ||
| listenerCount(event: string): number; | ||
| listeners(event: string): Function[]; | ||
| rawListeners(event: string): Function[]; | ||
| emit(event: string, ...args: any[]): boolean; | ||
| eventNames(): (string | symbol)[]; | ||
| } | ||
| interface Application { | ||
| [Symbol.dispose](): void; | ||
| whenReady(options?: ApplicationWhenReadyOptions): Promise<void>; | ||
| on<K extends keyof ApplicationEventMap>(event: K, listener: (payload: ApplicationEventMap[K]) => void): this; | ||
| on(event: string, listener: (...args: any[]) => void): this; | ||
| once<K extends keyof ApplicationEventMap>(event: K, listener: (payload: ApplicationEventMap[K]) => void): this; | ||
| once(event: string, listener: (...args: any[]) => void): this; | ||
| off<K extends keyof ApplicationEventMap>(event: K, listener: (payload: ApplicationEventMap[K]) => void): this; | ||
| off(event: string, listener: (...args: any[]) => void): this; | ||
| addListener<K extends keyof ApplicationEventMap>( | ||
| event: K, | ||
| listener: (payload: ApplicationEventMap[K]) => void, | ||
| ): this; | ||
| addListener(event: string, listener: (...args: any[]) => void): this; | ||
| removeListener<K extends keyof ApplicationEventMap>( | ||
| event: K, | ||
| listener: (payload: ApplicationEventMap[K]) => void, | ||
| ): this; | ||
| removeListener(event: string, listener: (...args: any[]) => void): this; | ||
| removeAllListeners(event?: string): this; | ||
| listenerCount(event: string): number; | ||
| listeners(event: string): Function[]; | ||
| rawListeners(event: string): Function[]; | ||
| emit(event: string, ...args: any[]): boolean; | ||
| eventNames(): (string | symbol)[]; | ||
| } | ||
| interface WebviewOptions { | ||
| /** | ||
| * Shared `WebContext` for cookie/data isolation across webviews. | ||
| * Create one with `app.createWebContext({ dataDirectory })` and pass it here. | ||
| */ | ||
| webContext?: import('./js-bindings').WebContext | null; | ||
| /** | ||
| * Synchronous navigation guard. Called with the target URL before every | ||
| * navigation; return `true` to allow, `false` to cancel. | ||
| * | ||
| * A `navigation` event is **always** emitted regardless of this handler. | ||
| */ | ||
| navigationHandler?: (url: string) => boolean; | ||
| } | ||
| interface BrowserWindow { | ||
| [Symbol.dispose](): void; | ||
| /** | ||
| * Register a custom protocol handler. | ||
| * | ||
| * The handler receives a global `Request` object and should return a | ||
| * global `Response` (compatible with Hono, itty-router, and any other | ||
| * Fetch-API framework), or a legacy `CustomProtocolResponse` plain object. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * // With Hono: | ||
| * win.registerProtocol('app', (req) => honoApp.fetch(req)); | ||
| * | ||
| * // With a plain Response: | ||
| * win.registerProtocol('app', async (req) => { | ||
| * const body = await readFile('./index.html'); | ||
| * return new Response(body, { headers: { 'Content-Type': 'text/html' } }); | ||
| * }); | ||
| * ``` | ||
| */ | ||
| registerProtocol( | ||
| name: string, | ||
| handler: (request: Request) => Response | CustomProtocolResponse | Promise<Response | CustomProtocolResponse>, | ||
| ): void; | ||
| on<K extends keyof BrowserWindowEventMap>(event: K, listener: (payload: BrowserWindowEventMap[K]) => void): this; | ||
| on(event: string, listener: (...args: any[]) => void): this; | ||
| once<K extends keyof BrowserWindowEventMap>(event: K, listener: (payload: BrowserWindowEventMap[K]) => void): this; | ||
| once(event: string, listener: (...args: any[]) => void): this; | ||
| off<K extends keyof BrowserWindowEventMap>(event: K, listener: (payload: BrowserWindowEventMap[K]) => void): this; | ||
| off(event: string, listener: (...args: any[]) => void): this; | ||
| addListener<K extends keyof BrowserWindowEventMap>( | ||
| event: K, | ||
| listener: (payload: BrowserWindowEventMap[K]) => void, | ||
| ): this; | ||
| addListener(event: string, listener: (...args: any[]) => void): this; | ||
| removeListener<K extends keyof BrowserWindowEventMap>( | ||
| event: K, | ||
| listener: (payload: BrowserWindowEventMap[K]) => void, | ||
| ): this; | ||
| removeListener(event: string, listener: (...args: any[]) => void): this; | ||
| removeAllListeners(event?: string): this; | ||
| listenerCount(event: string): number; | ||
| listeners(event: string): Function[]; | ||
| rawListeners(event: string): Function[]; | ||
| emit(event: string, ...args: any[]): boolean; | ||
| eventNames(): (string | symbol)[]; | ||
| } | ||
| interface Webview { | ||
| [Symbol.dispose](): void; | ||
| expose(name: string, target: ExposedTarget): void; | ||
| // EventEmitter — mirrors BrowserWindow events but for webview-level events. | ||
| on<K extends keyof WebviewEventMap>(event: K, listener: (payload: WebviewEventMap[K]) => void): this; | ||
| on(event: string, listener: (...args: any[]) => void): this; | ||
| once<K extends keyof WebviewEventMap>(event: K, listener: (payload: WebviewEventMap[K]) => void): this; | ||
| once(event: string, listener: (...args: any[]) => void): this; | ||
| off<K extends keyof WebviewEventMap>(event: K, listener: (payload: WebviewEventMap[K]) => void): this; | ||
| off(event: string, listener: (...args: any[]) => void): this; | ||
| addListener<K extends keyof WebviewEventMap>(event: K, listener: (payload: WebviewEventMap[K]) => void): this; | ||
| addListener(event: string, listener: (...args: any[]) => void): this; | ||
| removeListener<K extends keyof WebviewEventMap>(event: K, listener: (payload: WebviewEventMap[K]) => void): this; | ||
| removeListener(event: string, listener: (...args: any[]) => void): this; | ||
| removeAllListeners(event?: string): this; | ||
| listenerCount(event: string): number; | ||
| listeners(event: string): Function[]; | ||
| rawListeners(event: string): Function[]; | ||
| emit(event: string, ...args: any[]): boolean; | ||
| eventNames(): (string | symbol)[]; | ||
| } | ||
| interface WebContext { | ||
| [Symbol.dispose](): void; | ||
| } | ||
| } |
| const nativeBinding = require('./js-bindings.js'); | ||
| const { EventEmitter } = require('events'); | ||
| // ── Notifications ─────────────────────────────────────────────────────────── | ||
| class Notification { | ||
| #emitter = new EventEmitter(); | ||
| #native; | ||
| #handlers = new Map(); | ||
| #options; | ||
| #title; | ||
| constructor(title, options = {}) { | ||
| if (options.image !== undefined && typeof options.image !== 'string' && !Buffer.isBuffer(options.image)) { | ||
| throw new TypeError('Notification image must be a file path string or Buffer'); | ||
| } | ||
| if (options.actions !== undefined && !Array.isArray(options.actions)) { | ||
| throw new TypeError('Notification actions must be an array'); | ||
| } | ||
| const persistent = options.persistent ?? false; | ||
| const actions = (options.actions ?? []).map((action) => ({ | ||
| action: String(action.action), | ||
| title: String(action.title), | ||
| icon: action.icon === undefined ? '' : String(action.icon), | ||
| })); | ||
| if (actions.length > 0 && !persistent) { | ||
| throw new TypeError('Notification actions require persistent: true'); | ||
| } | ||
| this.#title = String(title); | ||
| this.#options = { | ||
| body: options.body ?? '', | ||
| icon: options.icon ?? '', | ||
| image: options.image ?? '', | ||
| badge: options.badge ?? '', | ||
| tag: options.tag ?? '', | ||
| data: options.data, | ||
| dir: options.dir ?? 'auto', | ||
| lang: options.lang ?? '', | ||
| renotify: options.renotify ?? false, | ||
| requireInteraction: options.requireInteraction ?? false, | ||
| persistent, | ||
| actions, | ||
| silent: options.silent ?? false, | ||
| timestamp: options.timestamp ?? Date.now(), | ||
| vibrate: options.vibrate ?? [], | ||
| }; | ||
| this.#native = new nativeBinding.NativeNotification( | ||
| { | ||
| title: this.#title, | ||
| body: this.#options.body || undefined, | ||
| icon: this.#options.icon || undefined, | ||
| imagePath: typeof this.#options.image === 'string' ? this.#options.image || undefined : undefined, | ||
| imageData: Buffer.isBuffer(this.#options.image) ? this.#options.image : undefined, | ||
| requireInteraction: this.#options.requireInteraction, | ||
| persistent: this.#options.persistent, | ||
| actions: this.#options.actions.map(({ action, title, icon }) => ({ | ||
| action, | ||
| title, | ||
| icon: icon || undefined, | ||
| })), | ||
| }, | ||
| (error, payload) => { | ||
| if (error) { | ||
| this.#dispatch({ event: 'error', error: error.message }); | ||
| } else { | ||
| this.#dispatch(payload); | ||
| } | ||
| }, | ||
| ); | ||
| } | ||
| static get permission() { | ||
| return 'granted'; | ||
| } | ||
| static requestPermission() { | ||
| return Promise.resolve('granted'); | ||
| } | ||
| #dispatch(payload) { | ||
| const type = payload.event; | ||
| const event = { | ||
| type, | ||
| target: this, | ||
| action: payload.action, | ||
| error: payload.error === undefined ? undefined : new Error(payload.error), | ||
| }; | ||
| this.#emitter.emit(type, event); | ||
| this.#handlers.get(type)?.call(this, event); | ||
| } | ||
| close() { | ||
| this.#native.close(); | ||
| } | ||
| on(event, listener) { | ||
| this.#emitter.on(event, listener); | ||
| return this; | ||
| } | ||
| once(event, listener) { | ||
| this.#emitter.once(event, listener); | ||
| return this; | ||
| } | ||
| off(event, listener) { | ||
| this.#emitter.off(event, listener); | ||
| return this; | ||
| } | ||
| addListener(event, listener) { | ||
| this.#emitter.addListener(event, listener); | ||
| return this; | ||
| } | ||
| removeListener(event, listener) { | ||
| this.#emitter.removeListener(event, listener); | ||
| return this; | ||
| } | ||
| removeAllListeners(event) { | ||
| this.#emitter.removeAllListeners(event); | ||
| return this; | ||
| } | ||
| listenerCount(event, listener) { | ||
| return this.#emitter.listenerCount(event, listener); | ||
| } | ||
| listeners(event) { | ||
| return this.#emitter.listeners(event); | ||
| } | ||
| rawListeners(event) { | ||
| return this.#emitter.rawListeners(event); | ||
| } | ||
| emit(event, ...args) { | ||
| return this.#emitter.emit(event, ...args); | ||
| } | ||
| eventNames() { | ||
| return this.#emitter.eventNames(); | ||
| } | ||
| get title() { | ||
| return this.#title; | ||
| } | ||
| get body() { | ||
| return this.#options.body; | ||
| } | ||
| get icon() { | ||
| return this.#options.icon; | ||
| } | ||
| get image() { | ||
| return this.#options.image; | ||
| } | ||
| get badge() { | ||
| return this.#options.badge; | ||
| } | ||
| get tag() { | ||
| return this.#options.tag; | ||
| } | ||
| get data() { | ||
| return this.#options.data; | ||
| } | ||
| get dir() { | ||
| return this.#options.dir; | ||
| } | ||
| get lang() { | ||
| return this.#options.lang; | ||
| } | ||
| get renotify() { | ||
| return this.#options.renotify; | ||
| } | ||
| get requireInteraction() { | ||
| return this.#options.requireInteraction; | ||
| } | ||
| get persistent() { | ||
| return this.#options.persistent; | ||
| } | ||
| get actions() { | ||
| return this.#options.actions; | ||
| } | ||
| get silent() { | ||
| return this.#options.silent; | ||
| } | ||
| get timestamp() { | ||
| return this.#options.timestamp; | ||
| } | ||
| get vibrate() { | ||
| return this.#options.vibrate; | ||
| } | ||
| get onclick() { | ||
| return this.#handlers.get('click') ?? null; | ||
| } | ||
| set onclick(listener) { | ||
| this.#setHandler('click', listener); | ||
| } | ||
| get onclose() { | ||
| return this.#handlers.get('close') ?? null; | ||
| } | ||
| set onclose(listener) { | ||
| this.#setHandler('close', listener); | ||
| } | ||
| get onerror() { | ||
| return this.#handlers.get('error') ?? null; | ||
| } | ||
| set onerror(listener) { | ||
| this.#setHandler('error', listener); | ||
| } | ||
| get onshow() { | ||
| return this.#handlers.get('show') ?? null; | ||
| } | ||
| set onshow(listener) { | ||
| this.#setHandler('show', listener); | ||
| } | ||
| #setHandler(type, listener) { | ||
| if (listener == null) { | ||
| this.#handlers.delete(type); | ||
| return; | ||
| } | ||
| if (typeof listener !== 'function') { | ||
| throw new TypeError(`on${type} must be a function or null`); | ||
| } | ||
| this.#handlers.set(type, listener); | ||
| } | ||
| } | ||
| // Patch the native Application prototype with non-blocking run/stop. | ||
| // A WeakMap stores each instance's timer so no extra class wrapper is needed. | ||
| const _timers = new WeakMap(); | ||
| nativeBinding.Application.prototype.run = function run(options = {}) { | ||
| const interval = options.interval ?? 16; | ||
| const shouldRef = options.ref ?? true; | ||
| if (_timers.has(this)) return; | ||
| const timer = setInterval(() => { | ||
| if (!this.pumpEvents()) this.stop(); | ||
| }, interval); | ||
| if (!shouldRef) timer.unref(); | ||
| _timers.set(this, timer); | ||
| }; | ||
| nativeBinding.Application.prototype.stop = function stop() { | ||
| const timer = _timers.get(this); | ||
| if (timer === undefined) return; | ||
| clearInterval(timer); | ||
| _timers.delete(this); | ||
| }; | ||
| const _nativeExit = nativeBinding.Application.prototype.exit; | ||
| nativeBinding.Application.prototype.exit = function exit() { | ||
| this.stop(); | ||
| return _nativeExit.call(this); | ||
| }; | ||
| nativeBinding.Application.prototype[Symbol.dispose] = function dispose() { | ||
| this.exit(); | ||
| }; | ||
| for (const Type of [ | ||
| nativeBinding.BrowserWindow, | ||
| nativeBinding.Webview, | ||
| nativeBinding.WebContext, | ||
| nativeBinding.TrayIcon, | ||
| ]) { | ||
| if (Type?.prototype?.dispose) { | ||
| Type.prototype[Symbol.dispose] = function dispose() { | ||
| this.dispose(); | ||
| }; | ||
| } | ||
| } | ||
| for (const Type of [ | ||
| nativeBinding.BrowserWindow, | ||
| nativeBinding.Webview, | ||
| nativeBinding.WebContext, | ||
| nativeBinding.TrayIcon, | ||
| ]) { | ||
| if (!Type?.prototype?.isDisposed) continue; | ||
| for (const name of Object.getOwnPropertyNames(Type.prototype)) { | ||
| if (name === 'constructor' || name === 'dispose' || name === 'isDisposed') continue; | ||
| const descriptor = Object.getOwnPropertyDescriptor(Type.prototype, name); | ||
| if (typeof descriptor?.value !== 'function') continue; | ||
| const nativeMethod = descriptor.value; | ||
| Type.prototype[name] = function (...args) { | ||
| if (this.isDisposed()) { | ||
| throw new Error(`${Type.name} has been disposed`); | ||
| } | ||
| return nativeMethod.apply(this, args); | ||
| }; | ||
| } | ||
| } | ||
| // ── TrayIcon EventEmitter ──────────────────────────────────────────────────── | ||
| const _trayEmitters = new WeakMap(); | ||
| function _getTrayEmitter(tray) { | ||
| if (!_trayEmitters.has(tray)) { | ||
| const emitter = new EventEmitter(); | ||
| _trayEmitters.set(tray, emitter); | ||
| tray._onTrayEvent(function (payload) { | ||
| emitter.emit(payload.event, payload); | ||
| }); | ||
| } | ||
| return _trayEmitters.get(tray); | ||
| } | ||
| [ | ||
| 'on', | ||
| 'once', | ||
| 'off', | ||
| 'addListener', | ||
| 'removeListener', | ||
| 'removeAllListeners', | ||
| 'listenerCount', | ||
| 'listeners', | ||
| 'rawListeners', | ||
| 'emit', | ||
| 'eventNames', | ||
| ].forEach((method) => { | ||
| nativeBinding.TrayIcon.prototype[method] = function (...args) { | ||
| const emitter = _getTrayEmitter(this); | ||
| const result = emitter[method](...args); | ||
| return result === emitter ? this : result; | ||
| }; | ||
| }); | ||
| // ── Application EventEmitter ───────────────────────────────────────────────── | ||
| // Maps WebviewApplicationEvent numeric values (from Rust enum order) to names. | ||
| const _applicationEventNames = [ | ||
| 'window-close-requested', // 0 WindowCloseRequested | ||
| 'application-close-requested', // 1 ApplicationCloseRequested | ||
| 'custom-menu-click', // 2 CustomMenuClick | ||
| 'ready', // 3 Ready | ||
| ]; | ||
| const _applicationEmitters = new WeakMap(); | ||
| function _getApplicationEmitter(app) { | ||
| if (!_applicationEmitters.has(app)) { | ||
| const emitter = new EventEmitter(); | ||
| _applicationEmitters.set(app, emitter); | ||
| app.onEvent(function (payload) { | ||
| const name = _applicationEventNames[payload.event]; | ||
| if (name !== undefined) emitter.emit(name, payload); | ||
| }); | ||
| } | ||
| return _applicationEmitters.get(app); | ||
| } | ||
| nativeBinding.Application.prototype.whenReady = function whenReady(options = {}) { | ||
| const { autoRun = true, interval, ref } = options; | ||
| if (!autoRun) { | ||
| if (Object.prototype.hasOwnProperty.call(options, 'interval')) { | ||
| throw new TypeError('interval is not supported when autoRun is false'); | ||
| } | ||
| if (Object.prototype.hasOwnProperty.call(options, 'ref')) { | ||
| throw new TypeError('ref is not supported when autoRun is false'); | ||
| } | ||
| } | ||
| const ready = this.isReady() | ||
| ? Promise.resolve() | ||
| : new Promise((resolve) => { | ||
| nativeBinding.Application.prototype.once.call(this, 'ready', resolve); | ||
| }); | ||
| if (autoRun) { | ||
| const runOptions = {}; | ||
| if (interval !== undefined) runOptions.interval = interval; | ||
| if (ref !== undefined) runOptions.ref = ref; | ||
| this.run(runOptions); | ||
| } | ||
| return ready; | ||
| }; | ||
| [ | ||
| 'on', | ||
| 'once', | ||
| 'off', | ||
| 'addListener', | ||
| 'removeListener', | ||
| 'removeAllListeners', | ||
| 'listenerCount', | ||
| 'listeners', | ||
| 'rawListeners', | ||
| 'emit', | ||
| 'eventNames', | ||
| ].forEach((method) => { | ||
| nativeBinding.Application.prototype[method] = function (...args) { | ||
| const emitter = _getApplicationEmitter(this); | ||
| const result = emitter[method](...args); | ||
| return result === emitter ? this : result; | ||
| }; | ||
| }); | ||
| // ── BrowserWindow EventEmitter ──────────────────────────────────────────────── | ||
| // Maps WindowEventType numeric values (from Rust enum order) to event names. | ||
| const _windowEventNames = [ | ||
| 'move', // 0 Moved | ||
| 'resize', // 1 Resized | ||
| 'close', // 2 CloseRequested | ||
| 'focus', // 3 Focused | ||
| 'blur', // 4 Blurred | ||
| 'mouse-enter', // 5 MouseEnter | ||
| 'mouse-leave', // 6 MouseLeave | ||
| 'mouse-move', // 7 MouseMove | ||
| 'mouse-down', // 8 MouseDown | ||
| 'mouse-up', // 9 MouseUp | ||
| 'scroll', // 10 Scroll | ||
| 'key-down', // 11 KeyDown | ||
| 'key-up', // 12 KeyUp | ||
| 'file-drop', // 13 FileDrop | ||
| 'file-hover', // 14 FileHover | ||
| 'file-hover-cancelled', // 15 FileHoverCancelled | ||
| 'scale-factor-changed', // 16 ScaleFactorChanged | ||
| 'theme-changed', // 17 ThemeChanged | ||
| 'ime', // 18 Ime | ||
| 'touch', // 19 Touch | ||
| ]; | ||
| const _windowEmitters = new WeakMap(); | ||
| function _getWindowEmitter(win) { | ||
| if (!_windowEmitters.has(win)) { | ||
| const emitter = new EventEmitter(); | ||
| _windowEmitters.set(win, emitter); | ||
| win._onWindowEvent(function (payload) { | ||
| const name = _windowEventNames[payload.event]; | ||
| if (name !== undefined) emitter.emit(name, payload); | ||
| }); | ||
| } | ||
| return _windowEmitters.get(win); | ||
| } | ||
| [ | ||
| 'on', | ||
| 'once', | ||
| 'off', | ||
| 'addListener', | ||
| 'removeListener', | ||
| 'removeAllListeners', | ||
| 'listenerCount', | ||
| 'listeners', | ||
| 'rawListeners', | ||
| 'emit', | ||
| 'eventNames', | ||
| ].forEach((method) => { | ||
| nativeBinding.BrowserWindow.prototype[method] = function (...args) { | ||
| const result = _getWindowEmitter(this)[method](...args); | ||
| // Return `this` for chainable methods, otherwise the emitter's return value. | ||
| return result === _getWindowEmitter(this) ? this : result; | ||
| }; | ||
| }); | ||
| // ── BrowserWindow.registerProtocol ─────────────────────────────────────────── | ||
| // Wraps the low-level `_registerProtocol(name, (payloadJson) => void)` native | ||
| // API with a clean async handler: `(request: Request) => Promise<Response>`. | ||
| // The handler receives a global `Request` object and should return a global | ||
| // `Response` (or a legacy `CustomProtocolResponse` plain object for compat). | ||
| // This allows frameworks like Hono to be used directly: | ||
| // win.registerProtocol('app', (req) => honoApp.fetch(req)); | ||
| nativeBinding.BrowserWindow.prototype.registerProtocol = function registerProtocol(name, asyncHandler) { | ||
| const win = this; | ||
| win._registerProtocol(name, function (payloadJson) { | ||
| let parsed; | ||
| try { | ||
| parsed = JSON.parse(payloadJson); | ||
| } catch { | ||
| return; | ||
| } | ||
| const { id, url, method, headers: rawHeaders, body: bodyArr } = parsed; | ||
| // Build a global Headers object | ||
| const headersObj = new Headers(); | ||
| for (const { key, value } of rawHeaders ?? []) { | ||
| if (value != null) headersObj.set(key, value); | ||
| } | ||
| // Build a global Request — GET/HEAD cannot carry a body | ||
| const canHaveBody = !['GET', 'HEAD'].includes(method.toUpperCase()); | ||
| const reqInit = { method, headers: headersObj }; | ||
| if (canHaveBody && Array.isArray(bodyArr) && bodyArr.length > 0) { | ||
| reqInit.body = Buffer.from(bodyArr); | ||
| } | ||
| const request = new Request(url, reqInit); | ||
| Promise.resolve(asyncHandler(request)) | ||
| .then(async (resp) => { | ||
| // Accept a global Response object (from Hono / fetch-compatible handlers) | ||
| if (typeof Response !== 'undefined' && resp instanceof Response) { | ||
| const bodyBuf = Buffer.from(await resp.arrayBuffer()); | ||
| const contentType = resp.headers.get('content-type') ?? 'application/octet-stream'; | ||
| const extraHeaders = []; | ||
| resp.headers.forEach((value, key) => { | ||
| if (key.toLowerCase() !== 'content-type') extraHeaders.push({ key, value }); | ||
| }); | ||
| return win._completeProtocol(id, { | ||
| statusCode: resp.status, | ||
| body: bodyBuf, | ||
| mimeType: contentType, | ||
| headers: extraHeaders, | ||
| }); | ||
| } | ||
| // Legacy CustomProtocolResponse plain object | ||
| return win._completeProtocol(id, resp); | ||
| }) | ||
| .catch((err) => | ||
| win._completeProtocol(id, { | ||
| statusCode: 500, | ||
| body: Buffer.from(String(err?.message ?? err)), | ||
| mimeType: 'text/plain', | ||
| }), | ||
| ); | ||
| }); | ||
| }; | ||
| // ── Webview EventEmitter ────────────────────────────────────────────────────── | ||
| // Maps WebviewEventType numeric values (Rust enum order) to JS event names. | ||
| const _webviewEventNames = [ | ||
| 'page-load-started', // 0 PageLoadStarted | ||
| 'page-load-finished', // 1 PageLoadFinished | ||
| 'title-changed', // 2 TitleChanged | ||
| 'download-started', // 3 DownloadStarted | ||
| 'download-completed', // 4 DownloadCompleted | ||
| 'navigation', // 5 NavigationStarted | ||
| 'new-window', // 6 NewWindowRequested | ||
| ]; | ||
| const _webviewEmitters = new WeakMap(); | ||
| function _attachWebviewEmitter(webview, emitter) { | ||
| [ | ||
| 'on', | ||
| 'once', | ||
| 'off', | ||
| 'addListener', | ||
| 'removeListener', | ||
| 'removeAllListeners', | ||
| 'listenerCount', | ||
| 'listeners', | ||
| 'rawListeners', | ||
| 'emit', | ||
| 'eventNames', | ||
| ].forEach((method) => { | ||
| webview[method] = function (...args) { | ||
| const result = emitter[method](...args); | ||
| return result === emitter ? webview : result; | ||
| }; | ||
| }); | ||
| _webviewEmitters.set(webview, emitter); | ||
| } | ||
| // ── BrowserWindow.createWebview wrapper ────────────────────────────────────── | ||
| // Intercepts `createWebview(options)` to: | ||
| // - Extract `webContext` and `navigationHandler` from options | ||
| // - Pre-register event dispatch and sync guard callbacks before the native build | ||
| // - Attach an EventEmitter to the returned Webview | ||
| const _nativeCreateWebview = nativeBinding.BrowserWindow.prototype.createWebview; | ||
| nativeBinding.BrowserWindow.prototype.createWebview = function createWebview(opts) { | ||
| const { webContext = null, navigationHandler = null, ...rustOpts } = opts ?? {}; | ||
| const emitter = new EventEmitter(); | ||
| // Always pre-register the event dispatch; the wry handlers call it for every | ||
| // page-load / title / download / navigation event. | ||
| this._setPendingWebviewEventCallback(function (error, payload) { | ||
| if (error) throw error; | ||
| const name = _webviewEventNames[payload.event]; | ||
| if (name !== undefined) emitter.emit(name, payload); | ||
| }); | ||
| if (typeof navigationHandler === 'function') { | ||
| this._setPendingWebviewNavigationHandler(navigationHandler); | ||
| } | ||
| const webview = _nativeCreateWebview.call(this, rustOpts, webContext); | ||
| // Clear so subsequent createWebview calls start with a clean slate. | ||
| this._clearPendingWebviewHandlers(); | ||
| _attachWebviewEmitter(webview, emitter); | ||
| return webview; | ||
| }; | ||
| // ── Webview.expose ──────────────────────────────────────────────────────────── | ||
| // Injects a proxy object at `window[name]` in the page. | ||
| // Static (non-function) properties are serialised once at call time. | ||
| // Every function call from the page side is async (returns a Promise). | ||
| // Throws SerializationError for non-JSON-serialisable args or return values. | ||
| class SerializationError extends Error { | ||
| constructor(msg) { | ||
| super(msg); | ||
| this.name = 'SerializationError'; | ||
| } | ||
| } | ||
| const _exposedNamespaces = new WeakMap(); | ||
| function jsonValue(value, context) { | ||
| try { | ||
| const json = JSON.stringify(value); | ||
| if (json === undefined) throw new TypeError('JSON.stringify returned undefined'); | ||
| return json; | ||
| } catch { | ||
| throw new SerializationError(`${context} is not JSON-serialisable`); | ||
| } | ||
| } | ||
| function sendExposeError(webview, id, message, name = 'Error') { | ||
| webview.evaluateScript( | ||
| `window.__webviewjs__&&window.__webviewjs__.reject(${Number(id)},${JSON.stringify(String(message))},${JSON.stringify(name)})`, | ||
| ); | ||
| } | ||
| nativeBinding.Webview.prototype.expose = function expose(name, target) { | ||
| const self = this; | ||
| if (!/^[A-Za-z_$][\w$]*$/u.test(name)) { | ||
| throw new TypeError('expose(): name must be a valid JavaScript identifier'); | ||
| } | ||
| if (target === null || (typeof target !== 'object' && typeof target !== 'function')) { | ||
| throw new TypeError('expose(): target must be an object'); | ||
| } | ||
| const namespaces = _exposedNamespaces.get(self) ?? new Set(); | ||
| if (namespaces.has(name)) { | ||
| throw new Error(`expose(): namespace "${name}" is already registered`); | ||
| } | ||
| const statics = {}; | ||
| const functions = new Map(); | ||
| for (const [k, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(target))) { | ||
| if (!descriptor.enumerable || !Object.hasOwn(descriptor, 'value')) continue; | ||
| const { value: v } = descriptor; | ||
| if (typeof v === 'function') { | ||
| functions.set(k, v); | ||
| } else { | ||
| JSON.parse(jsonValue(v, `expose(): value for property "${k}"`)); | ||
| statics[k] = v; | ||
| } | ||
| } | ||
| self._exposeInternal(name, jsonValue(statics, 'expose(): static properties'), [...functions.keys()], function (call) { | ||
| const { ns: _ns, method, id, argsJson } = call; | ||
| const fn = functions.get(method); | ||
| if (fn === undefined) { | ||
| sendExposeError(self, id, `No such method: ${method}`); | ||
| return; | ||
| } | ||
| let args; | ||
| try { | ||
| args = JSON.parse(argsJson); | ||
| } catch { | ||
| sendExposeError(self, id, 'Argument parse error', 'SerializationError'); | ||
| return; | ||
| } | ||
| Promise.resolve(fn.apply(target, args)) | ||
| .then((result) => { | ||
| try { | ||
| const resultJson = jsonValue(result, 'Return value'); | ||
| self.evaluateScript(`window.__webviewjs__&&window.__webviewjs__.resolve(${Number(id)},${resultJson})`); | ||
| } catch { | ||
| sendExposeError(self, id, 'Return value is not JSON-serialisable', 'SerializationError'); | ||
| } | ||
| }) | ||
| .catch((err) => { | ||
| sendExposeError( | ||
| self, | ||
| id, | ||
| String(err?.message ?? err), | ||
| err?.name === 'SerializationError' ? 'SerializationError' : 'Error', | ||
| ); | ||
| }); | ||
| }); | ||
| namespaces.add(name); | ||
| _exposedNamespaces.set(self, namespaces); | ||
| }; | ||
| module.exports = nativeBinding; | ||
| module.exports.SerializationError = SerializationError; | ||
| module.exports.Notification = Notification; | ||
| // Auto-generated exports by postbuild.js. Do not edit directly. | ||
| module.exports.Application = nativeBinding.Application; | ||
| module.exports.BrowserWindow = nativeBinding.BrowserWindow; | ||
| module.exports.NativeNotification = nativeBinding.NativeNotification; | ||
| module.exports.JsNotification = nativeBinding.JsNotification; | ||
| module.exports.TrayIcon = nativeBinding.TrayIcon; | ||
| module.exports.JsTrayIcon = nativeBinding.JsTrayIcon; | ||
| module.exports.WebContext = nativeBinding.WebContext; | ||
| module.exports.JsWebContext = nativeBinding.JsWebContext; | ||
| module.exports.Webview = nativeBinding.Webview; | ||
| module.exports.JsWebview = nativeBinding.JsWebview; | ||
| module.exports.ControlFlow = nativeBinding.ControlFlow; | ||
| module.exports.JsControlFlow = nativeBinding.JsControlFlow; | ||
| module.exports.CursorType = nativeBinding.CursorType; | ||
| module.exports.FullscreenType = nativeBinding.FullscreenType; | ||
| module.exports.getWebviewVersion = nativeBinding.getWebviewVersion; | ||
| module.exports.IosValidOrientations = nativeBinding.IosValidOrientations; | ||
| module.exports.ProgressBarState = nativeBinding.ProgressBarState; | ||
| module.exports.JsProgressBarState = nativeBinding.JsProgressBarState; | ||
| module.exports.Theme = nativeBinding.Theme; | ||
| module.exports.VERSION = nativeBinding.VERSION; | ||
| module.exports.WebviewApplicationEvent = nativeBinding.WebviewApplicationEvent; | ||
| module.exports.WebviewEventType = nativeBinding.WebviewEventType; | ||
| module.exports.WindowCommand = nativeBinding.WindowCommand; | ||
| module.exports.WindowEventType = nativeBinding.WindowEventType; |
| /* auto-generated by NAPI-RS */ | ||
| /* eslint-disable */ | ||
| export declare class Application { | ||
| constructor(options?: ApplicationOptions | undefined | null); | ||
| onEvent(handler?: ((arg: ApplicationEvent) => void) | undefined | null): void; | ||
| bind(handler?: ((arg: ApplicationEvent) => void) | undefined | null): void; | ||
| isReady(): boolean; | ||
| exit(): void; | ||
| /** Creates a new WebContext with the given options. */ | ||
| createWebContext(options?: WebContextOptions | undefined | null): JsWebContext; | ||
| createTrayIcon(options: TrayIconOptions): JsTrayIcon; | ||
| createBrowserWindow(options?: BrowserWindowOptions | undefined | null): BrowserWindow; | ||
| createChildBrowserWindow(options?: BrowserWindowOptions | undefined | null): BrowserWindow; | ||
| setMenu(menuOptions?: MenuOptions | undefined | null): void; | ||
| /** | ||
| * Pump the tao event loop once without blocking. Returns `true` while | ||
| * the app is alive, `false` when it should stop. Drive this from a JS | ||
| * `setInterval` via the `run()` wrapper in `index.js`. | ||
| */ | ||
| pumpEvents(): boolean; | ||
| /** Run the application event loop. */ | ||
| run(options?: ApplicationRunOptions | undefined | null): void; | ||
| } | ||
| export declare class BrowserWindow { | ||
| _registerProtocol(name: string, handler: (arg: string) => void): void; | ||
| _completeProtocol(id: number, response: CustomProtocolResponse): void; | ||
| createWebview(options?: WebviewOptions | undefined | null, webContext?: JsWebContext | undefined | null): JsWebview; | ||
| _setPendingWebviewEventCallback(handler: (err: Error | null, arg: WebviewEventPayload) => any): void; | ||
| _setPendingWebviewNavigationHandler(handler: (arg: string) => boolean): void; | ||
| _clearPendingWebviewHandlers(): void; | ||
| get isChild(): boolean; | ||
| getNativeHandle(): bigint; | ||
| isFocused(): boolean; | ||
| isVisible(): boolean; | ||
| isDecorated(): boolean; | ||
| isClosable(): boolean; | ||
| isMaximizable(): boolean; | ||
| isMinimizable(): boolean; | ||
| isMaximized(): boolean; | ||
| isMinimized(): boolean; | ||
| isResizable(): boolean; | ||
| setTitle(title: string): void; | ||
| get title(): string; | ||
| setClosable(closable: boolean): void; | ||
| setMaximizable(maximizable: boolean): void; | ||
| setMinimizable(minimizable: boolean): void; | ||
| setResizable(resizable: boolean): void; | ||
| setSize(width: number, height: number, logical?: boolean | undefined | null): Dimensions | null; | ||
| setMinSize(width: number, height: number, logical?: boolean | undefined | null): void; | ||
| getInnerSize(logical?: boolean | undefined | null): Dimensions; | ||
| setMaxSize(width: number, height: number, logical?: boolean | undefined | null): void; | ||
| getOuterSize(logical?: boolean | undefined | null): Dimensions; | ||
| openFileDialog(options?: FileDialogOptions | undefined | null): Array<string>; | ||
| id(): number; | ||
| hasMenu(): boolean; | ||
| dispose(): void; | ||
| isDisposed(): boolean; | ||
| _onWindowEvent(handler?: ((arg: WindowEventPayload) => void) | undefined | null): void; | ||
| get theme(): Theme; | ||
| setTheme(theme: Theme): void; | ||
| setWindowIcon( | ||
| icon: Uint8Array | Array<number>, | ||
| width?: number | undefined | null, | ||
| height?: number | undefined | null, | ||
| ): void; | ||
| removeWindowIcon(): void; | ||
| setEnable(enabled: boolean): void; | ||
| setTaskbarIcon( | ||
| icon: Uint8Array | Array<number>, | ||
| width?: number | undefined | null, | ||
| height?: number | undefined | null, | ||
| ): void; | ||
| removeTaskbarIcon(): void; | ||
| setUndecoratedShadow(shadow: boolean): void; | ||
| getNativeHandleAnyThread(): bigint; | ||
| simpleFullscreen(): boolean; | ||
| setSimpleFullscreen(fullscreen: boolean): boolean; | ||
| hasShadow(): boolean; | ||
| setHasShadow(value: boolean): void; | ||
| setTabbingIdentifier(identifier: string): void; | ||
| tabbingIdentifier(): string; | ||
| isDocumentEdited(): boolean; | ||
| setDocumentEdited(edited: boolean): void; | ||
| getWaylandSurface(): bigint; | ||
| setIosScaleFactor(value: number): void; | ||
| setValidOrientations(value: IosValidOrientations): void; | ||
| setPrefersHomeIndicatorHidden(value: boolean): void; | ||
| setPreferredScreenEdgesDeferringSystemGestures(edges: number): void; | ||
| setPrefersStatusBarHidden(value: boolean): void; | ||
| androidContentRect(): AndroidContentRect; | ||
| androidConfig(): string; | ||
| setVisible(visible: boolean): void; | ||
| setProgressBar(state: JsProgressBar): void; | ||
| setMaximized(value: boolean): void; | ||
| setMinimized(value: boolean): void; | ||
| focus(): void; | ||
| getAvailableMonitors(): Array<Monitor>; | ||
| getCurrentMonitor(): Monitor | null; | ||
| getPrimaryMonitor(): Monitor | null; | ||
| getMonitorFromPoint(x: number, y: number): Monitor | null; | ||
| setContentProtection(enabled: boolean): void; | ||
| setAlwaysOnTop(enabled: boolean): void; | ||
| setAlwaysOnBottom(enabled: boolean): void; | ||
| setDecorations(enabled: boolean): void; | ||
| get fullscreen(): FullscreenType | null; | ||
| setFullscreen(fullscreenType?: FullscreenType | undefined | null): void; | ||
| close(): void; | ||
| hide(): void; | ||
| show(): void; | ||
| setPosition(x: number, y: number, logical?: boolean | undefined | null): void; | ||
| getPosition(logical?: boolean | undefined | null): Position; | ||
| center(): void; | ||
| get width(): number; | ||
| get height(): number; | ||
| get x(): number; | ||
| get y(): number; | ||
| scaleFactor(): number; | ||
| setCursor(cursor: CursorType): void; | ||
| setCursorVisible(visible: boolean): void; | ||
| setCursorPosition(x: number, y: number): void; | ||
| setIgnoreCursorEvents(ignore: boolean): void; | ||
| setSkipTaskbar(skip: boolean): void; | ||
| requestRedraw(): void; | ||
| } | ||
| export declare class NativeNotification { | ||
| constructor(options: NativeNotificationOptions, callback: (err: Error | null, arg: NotificationEventPayload) => any); | ||
| close(): void; | ||
| } | ||
| export type JsNotification = NativeNotification; | ||
| export declare class TrayIcon { | ||
| constructor(); | ||
| get id(): string; | ||
| _onTrayEvent(handler?: ((arg: TrayEventPayload) => void) | undefined | null): void; | ||
| setIcon( | ||
| icon: Uint8Array | Array<number>, | ||
| width?: number | undefined | null, | ||
| height?: number | undefined | null, | ||
| ): void; | ||
| removeIcon(): void; | ||
| setMenu(menu?: MenuOptions | undefined | null): void; | ||
| setTooltip(tooltip?: string | undefined | null): void; | ||
| setTitle(title?: string | undefined | null): void; | ||
| setVisible(visible: boolean): void; | ||
| setIconAsTemplate(value: boolean): void; | ||
| setShowMenuOnLeftClick(value: boolean): void; | ||
| setShowMenuOnRightClick(value: boolean): void; | ||
| showMenu(): void; | ||
| rect(): TrayRect | null; | ||
| dispose(): void; | ||
| isDisposed(): boolean; | ||
| } | ||
| export type JsTrayIcon = TrayIcon; | ||
| export declare class WebContext { | ||
| /** Not supported. Use `app.createWebContext(options)` instead. */ | ||
| constructor(); | ||
| /** A reference to the data directory the context was created with. */ | ||
| get dataDirectory(): string | null; | ||
| /** Check if a custom protocol has been registered on this context. */ | ||
| isCustomProtocolRegistered(scheme: string): boolean; | ||
| /** | ||
| * Set if this context allows automation. | ||
| * Note: this is currently only enforced on Linux, and has the stipulation that only 1 context allows automation at a time. | ||
| */ | ||
| setAllowsAutomation(flag: boolean): void; | ||
| dispose(): void; | ||
| isDisposed(): boolean; | ||
| } | ||
| export type JsWebContext = WebContext; | ||
| export declare class Webview { | ||
| constructor(); | ||
| onIpcMessage(handler?: ((arg: IpcMessage) => void) | undefined | null): void; | ||
| dispose(): void; | ||
| isDisposed(): boolean; | ||
| /** | ||
| * Low-level method used by the JS `expose()` wrapper. | ||
| * | ||
| * Injects a page script that creates `window[name]` as an object with: | ||
| * - static values from `statics_json` (a JSON object string) | ||
| * - async function stubs for each name in `func_names` | ||
| * | ||
| * When the page calls one of the stubs the call is routed back here via | ||
| * the internal IPC channel and dispatched to `handler`. `handler` is | ||
| * responsible for calling `evaluateScript` to send the response. | ||
| */ | ||
| _exposeInternal( | ||
| name: string, | ||
| staticsJson: string, | ||
| funcNames: Array<string>, | ||
| handler: (arg: ExposeCallData) => void, | ||
| ): void; | ||
| print(): void; | ||
| zoom(scaleFactor: number): void; | ||
| setWebviewVisibility(visible: boolean): void; | ||
| isDevtoolsOpen(): boolean; | ||
| openDevtools(): void; | ||
| closeDevtools(): void; | ||
| loadUrl(url: string): void; | ||
| loadHtml(html: string): void; | ||
| evaluateScript(js: string): void; | ||
| evaluateScriptWithCallback(js: string, callback: (err: Error | null, arg: string) => any): void; | ||
| reload(): void; | ||
| /** The URL the webview is currently showing. */ | ||
| url(): string | null; | ||
| /** Webview width in logical pixels (same coordinate space as `set_bounds`). */ | ||
| get width(): number | null; | ||
| /** Webview height in logical pixels (same coordinate space as `set_bounds`). */ | ||
| get height(): number | null; | ||
| /** Webview x offset from the window's top-left corner, in logical pixels. */ | ||
| get x(): number | null; | ||
| /** Webview y offset from the window's top-left corner, in logical pixels. */ | ||
| get y(): number | null; | ||
| /** Load `url` with additional HTTP request headers. */ | ||
| loadUrlWithHeaders(url: string, headers: Array<HeaderData>): void; | ||
| /** | ||
| * Return all cookies currently stored for `url`, or every cookie if `url` | ||
| * is `null` / `undefined`. | ||
| */ | ||
| getCookies(url?: string | undefined | null): Array<WebviewCookie>; | ||
| /** Store a cookie in the webview's session. */ | ||
| setCookie(cookie: WebviewCookie): void; | ||
| /** | ||
| * Delete a cookie by name. `domain` and `path` narrow the match; | ||
| * omit them to delete across all domains/paths. | ||
| */ | ||
| deleteCookie(name: string, domain?: string | undefined | null, path?: string | undefined | null): void; | ||
| /** Erase all cookies, cache, local storage, and IndexedDB data. */ | ||
| clearAllBrowsingData(): void; | ||
| /** | ||
| * Set the background colour shown before (or behind) page content. | ||
| * Values are 0-255. | ||
| */ | ||
| setBackgroundColor(r: number, g: number, b: number, a: number): void; | ||
| /** | ||
| * Return the webview's current bounds relative to the window, in logical | ||
| * pixels. | ||
| */ | ||
| getBounds(): WebviewBounds | null; | ||
| /** Reposition and resize the webview within its window. */ | ||
| setBounds(bounds: WebviewBounds): void; | ||
| /** Give keyboard focus to the webview content area. */ | ||
| focus(): void; | ||
| /** Return focus to the parent/host window. */ | ||
| focusParent(): void; | ||
| } | ||
| export type JsWebview = Webview; | ||
| export interface AndroidContentRect { | ||
| left: number; | ||
| top: number; | ||
| right: number; | ||
| bottom: number; | ||
| } | ||
| export interface ApplicationEvent { | ||
| event: WebviewApplicationEvent; | ||
| customMenuEvent?: CustomMenuEvent; | ||
| } | ||
| export interface ApplicationOptions { | ||
| controlFlow?: ControlFlow; | ||
| waitTime?: number; | ||
| exitCode?: number; | ||
| } | ||
| export interface ApplicationRunOptions { | ||
| /** The interval in milliseconds to pump events. Defaults to 16 (60 FPS). */ | ||
| interval?: number; | ||
| /** Whether to keep the event loop alive. Defaults to true. */ | ||
| ref?: boolean; | ||
| } | ||
| export interface BrowserWindowOptions { | ||
| menu?: MenuOptions; | ||
| showMenu?: boolean; | ||
| resizable?: boolean; | ||
| title?: string; | ||
| logical?: boolean; | ||
| width?: number; | ||
| height?: number; | ||
| x?: number; | ||
| y?: number; | ||
| contentProtection?: boolean; | ||
| alwaysOnTop?: boolean; | ||
| alwaysOnBottom?: boolean; | ||
| visible?: boolean; | ||
| decorations?: boolean; | ||
| visibleOnAllWorkspaces?: boolean; | ||
| maximized?: boolean; | ||
| maximizable?: boolean; | ||
| minimizable?: boolean; | ||
| focused?: boolean; | ||
| transparent?: boolean; | ||
| fullscreen?: FullscreenType; | ||
| windowsOwnerWindow?: bigint; | ||
| windowsTaskbarIcon?: TrayIconImage; | ||
| windowsNoRedirectionBitmap?: boolean; | ||
| windowsDragAndDrop?: boolean; | ||
| windowsSkipTaskbar?: boolean; | ||
| windowsClassName?: string; | ||
| windowsUndecoratedShadow?: boolean; | ||
| macosMovableByWindowBackground?: boolean; | ||
| macosTitlebarTransparent?: boolean; | ||
| macosTitleHidden?: boolean; | ||
| macosTitlebarHidden?: boolean; | ||
| macosTitlebarButtonsHidden?: boolean; | ||
| macosFullsizeContentView?: boolean; | ||
| macosDisallowHidpi?: boolean; | ||
| macosHasShadow?: boolean; | ||
| macosTabbingIdentifier?: string; | ||
| iosScaleFactor?: number; | ||
| iosValidOrientations?: IosValidOrientations; | ||
| iosPrefersHomeIndicatorHidden?: boolean; | ||
| iosDeferredSystemGestureEdges?: number; | ||
| iosPrefersStatusBarHidden?: boolean; | ||
| } | ||
| /** Kept for backward compat; no longer used internally. */ | ||
| export declare enum ControlFlow { | ||
| Poll = 0, | ||
| Wait = 1, | ||
| WaitUntil = 2, | ||
| Exit = 3, | ||
| ExitWithCode = 4, | ||
| } | ||
| /** Cursor shape passed to [`BrowserWindow::set_cursor`]. */ | ||
| export declare enum CursorType { | ||
| Default = 0, | ||
| Crosshair = 1, | ||
| Hand = 2, | ||
| Arrow = 3, | ||
| Move = 4, | ||
| Text = 5, | ||
| Wait = 6, | ||
| Help = 7, | ||
| Progress = 8, | ||
| NotAllowed = 9, | ||
| ContextMenu = 10, | ||
| Cell = 11, | ||
| VerticalText = 12, | ||
| Alias = 13, | ||
| Copy = 14, | ||
| NoDrop = 15, | ||
| Grab = 16, | ||
| Grabbing = 17, | ||
| ZoomIn = 18, | ||
| ZoomOut = 19, | ||
| ResizeEast = 20, | ||
| ResizeNorth = 21, | ||
| ResizeNorthEast = 22, | ||
| ResizeNorthWest = 23, | ||
| ResizeSouth = 24, | ||
| ResizeSouthEast = 25, | ||
| ResizeSouthWest = 26, | ||
| ResizeWest = 27, | ||
| ResizeEastWest = 28, | ||
| ResizeNorthSouth = 29, | ||
| ResizeNorthEastSouthWest = 30, | ||
| ResizeNorthWestSouthEast = 31, | ||
| ResizeColumn = 32, | ||
| ResizeRow = 33, | ||
| AllScroll = 34, | ||
| } | ||
| export interface CustomMenuEvent { | ||
| id: string; | ||
| windowId: number; | ||
| } | ||
| /** Incoming request delivered to a custom-protocol handler. */ | ||
| export interface CustomProtocolRequest { | ||
| url: string; | ||
| method: string; | ||
| headers: Array<HeaderData>; | ||
| body?: Buffer; | ||
| } | ||
| /** Response returned by a custom-protocol handler. */ | ||
| export interface CustomProtocolResponse { | ||
| /** HTTP status code. Defaults to 200. */ | ||
| statusCode?: number; | ||
| /** Extra response headers (e.g. `[{ key: "Cache-Control", value: "no-store" }]`). */ | ||
| headers?: Array<HeaderData>; | ||
| /** Response body bytes. */ | ||
| body: Buffer; | ||
| /** MIME type (e.g. `"text/html"`, `"application/javascript"`). */ | ||
| mimeType?: string; | ||
| } | ||
| export interface Dimensions { | ||
| width: number; | ||
| height: number; | ||
| } | ||
| /** Data sent to the expose handler when the page calls a proxied function. */ | ||
| export interface ExposeCallData { | ||
| ns: string; | ||
| method: string; | ||
| id: number; | ||
| argsJson: string; | ||
| } | ||
| export interface FileDialogOptions { | ||
| multiple?: boolean; | ||
| title?: string; | ||
| defaultPath?: string; | ||
| filters?: Array<FileFilter>; | ||
| } | ||
| export interface FileFilter { | ||
| name: string; | ||
| extensions: Array<string>; | ||
| } | ||
| export declare enum FullscreenType { | ||
| Exclusive = 0, | ||
| Borderless = 1, | ||
| } | ||
| export declare function getWebviewVersion(): string; | ||
| export interface HeaderData { | ||
| key: string; | ||
| value?: string; | ||
| } | ||
| export declare enum IosValidOrientations { | ||
| LandscapeAndPortrait = 0, | ||
| Landscape = 1, | ||
| Portrait = 2, | ||
| } | ||
| export interface IpcMessage { | ||
| body: Buffer; | ||
| method: string; | ||
| headers: Array<HeaderData>; | ||
| uri: string; | ||
| } | ||
| export interface JsProgressBar { | ||
| state?: ProgressBarState; | ||
| progress?: number; | ||
| } | ||
| export interface MenuItemOptions { | ||
| id?: string; | ||
| label?: string; | ||
| enabled?: boolean; | ||
| accelerator?: string; | ||
| submenu?: MenuOptions; | ||
| role?: string; | ||
| } | ||
| export interface MenuOptions { | ||
| items: Array<MenuItemOptions>; | ||
| } | ||
| export interface Monitor { | ||
| name?: string; | ||
| scaleFactor: number; | ||
| size: Dimensions; | ||
| position: Position; | ||
| videoModes: Array<VideoMode>; | ||
| } | ||
| export interface NativeNotificationAction { | ||
| action: string; | ||
| title: string; | ||
| icon?: string; | ||
| } | ||
| export interface NativeNotificationOptions { | ||
| title: string; | ||
| body?: string; | ||
| icon?: string; | ||
| imagePath?: string; | ||
| imageData?: Buffer; | ||
| requireInteraction?: boolean; | ||
| persistent: boolean; | ||
| actions: Array<NativeNotificationAction>; | ||
| } | ||
| export interface NotificationEventPayload { | ||
| event: string; | ||
| action?: string; | ||
| error?: string; | ||
| } | ||
| export interface Position { | ||
| x: number; | ||
| y: number; | ||
| } | ||
| export declare enum ProgressBarState { | ||
| None = 0, | ||
| Normal = 1, | ||
| Indeterminate = 2, | ||
| Paused = 3, | ||
| Error = 4, | ||
| } | ||
| export declare enum Theme { | ||
| Light = 0, | ||
| Dark = 1, | ||
| System = 2, | ||
| } | ||
| export interface TrayEventPayload { | ||
| event: string; | ||
| id: string; | ||
| x: number; | ||
| y: number; | ||
| rect: TrayRect; | ||
| button?: string; | ||
| buttonState?: string; | ||
| } | ||
| export interface TrayIconImage { | ||
| data: Buffer; | ||
| width?: number; | ||
| height?: number; | ||
| } | ||
| export interface TrayIconOptions { | ||
| id?: string; | ||
| icon?: TrayIconImage; | ||
| tooltip?: string; | ||
| title?: string; | ||
| menu?: MenuOptions; | ||
| iconIsTemplate?: boolean; | ||
| menuOnLeftClick?: boolean; | ||
| menuOnRightClick?: boolean; | ||
| } | ||
| export interface TrayRect { | ||
| x: number; | ||
| y: number; | ||
| width: number; | ||
| height: number; | ||
| } | ||
| /** The current version of the `@webviewjs/webview` package */ | ||
| export const VERSION: string; | ||
| export interface VideoMode { | ||
| size: Dimensions; | ||
| bitDepth: number; | ||
| refreshRate: number; | ||
| } | ||
| export interface WebContextOptions { | ||
| /** | ||
| * Whether the WebView window should have a custom user data path. | ||
| * This is useful in Windows when a bundled application can’t have the webview data inside Program Files. | ||
| */ | ||
| dataDirectory?: string; | ||
| /** | ||
| * Whether the WebView window should allow automation (e.g. for testing). | ||
| * Note: this is currently only enforced on Linux, and has the stipulation that only 1 context allows automation at a time. | ||
| */ | ||
| allowsAutomation?: boolean; | ||
| } | ||
| export declare enum WebviewApplicationEvent { | ||
| WindowCloseRequested = 0, | ||
| ApplicationCloseRequested = 1, | ||
| CustomMenuClick = 2, | ||
| Ready = 3, | ||
| } | ||
| export interface WebviewBounds { | ||
| x: number; | ||
| y: number; | ||
| width: number; | ||
| height: number; | ||
| } | ||
| export interface WebviewCookie { | ||
| name: string; | ||
| value: string; | ||
| domain?: string; | ||
| path?: string; | ||
| httpOnly?: boolean; | ||
| secure?: boolean; | ||
| /** `"strict"`, `"lax"`, or `"none"`. */ | ||
| sameSite?: string; | ||
| } | ||
| /** Payload delivered to the webview event dispatch callback. */ | ||
| export interface WebviewEventPayload { | ||
| event: WebviewEventType; | ||
| /** URL associated with the event (navigation, page load, download). */ | ||
| url?: string; | ||
| /** Document title for `TitleChanged` events. */ | ||
| title?: string; | ||
| /** Download success flag for `DownloadCompleted` events. */ | ||
| success?: boolean; | ||
| } | ||
| /** Event types fired by a Webview and surfaced as EventEmitter events in JS. */ | ||
| export declare enum WebviewEventType { | ||
| PageLoadStarted = 0, | ||
| PageLoadFinished = 1, | ||
| TitleChanged = 2, | ||
| DownloadStarted = 3, | ||
| DownloadCompleted = 4, | ||
| NavigationStarted = 5, | ||
| /** | ||
| * Fired when a page attempts to open a new browser window | ||
| * (`window.open`, `target="_blank"`, etc.). | ||
| */ | ||
| NewWindowRequested = 6, | ||
| } | ||
| export interface WebviewOptions { | ||
| url?: string; | ||
| html?: string; | ||
| width?: number; | ||
| height?: number; | ||
| x?: number; | ||
| y?: number; | ||
| enableDevtools?: boolean; | ||
| incognito?: boolean; | ||
| userAgent?: string; | ||
| child?: boolean; | ||
| preload?: string; | ||
| transparent?: boolean; | ||
| theme?: Theme; | ||
| hotkeysZoom?: boolean; | ||
| clipboard?: boolean; | ||
| autoplay?: boolean; | ||
| backForwardNavigationGestures?: boolean; | ||
| /** Custom name for the IPC global injected by wry (default: `"ipc"`). */ | ||
| ipcName?: string; | ||
| } | ||
| export declare enum WindowCommand { | ||
| Close = 0, | ||
| Show = 1, | ||
| Hide = 2, | ||
| } | ||
| export interface WindowEventPayload { | ||
| event: WindowEventType; | ||
| /** Physical x position (cursor or window). */ | ||
| x?: number; | ||
| /** Physical y position (cursor or window). */ | ||
| y?: number; | ||
| /** Physical width (resize event). */ | ||
| width?: number; | ||
| /** Physical height (resize event). */ | ||
| height?: number; | ||
| /** Mouse button index: 0=left, 1=middle, 2=right. */ | ||
| button?: number; | ||
| /** Horizontal scroll delta (pixels). */ | ||
| deltaX?: number; | ||
| /** Vertical scroll delta (pixels). */ | ||
| deltaY?: number; | ||
| /** Logical key name (DOM KeyboardEvent.key, e.g. "a", "Enter", "ArrowLeft"). */ | ||
| key?: string; | ||
| /** Physical key code (DOM KeyboardEvent.code, e.g. "KeyA", "ArrowLeft"). */ | ||
| code?: string; | ||
| /** Modifier bitmask: 1=Shift, 2=Ctrl, 4=Alt, 8=Meta/Super/Command. */ | ||
| modifiers?: number; | ||
| /** Whether this key event is a repeat from holding the key down. */ | ||
| isRepeat?: boolean; | ||
| /** File paths for FileDrop / FileHover events. */ | ||
| files?: Array<string>; | ||
| /** DPI scale factor for ScaleFactorChanged events. */ | ||
| scaleFactor?: number; | ||
| /** | ||
| * Text for Ime events (preedit string or committed text); | ||
| * "light" or "dark" for ThemeChanged events. | ||
| */ | ||
| text?: string; | ||
| /** Touch point identifier (cast from u64) for Touch events. */ | ||
| touchId?: number; | ||
| /** | ||
| * Phase string: "started"/"moved"/"ended"/"cancelled" for Touch; | ||
| * "enabled"/"preedit"/"commit"/"disabled" for Ime events. | ||
| */ | ||
| phase?: string; | ||
| } | ||
| export declare enum WindowEventType { | ||
| Moved = 0, | ||
| Resized = 1, | ||
| CloseRequested = 2, | ||
| Focused = 3, | ||
| Blurred = 4, | ||
| MouseEnter = 5, | ||
| MouseLeave = 6, | ||
| MouseMove = 7, | ||
| MouseDown = 8, | ||
| MouseUp = 9, | ||
| Scroll = 10, | ||
| KeyDown = 11, | ||
| KeyUp = 12, | ||
| FileDrop = 13, | ||
| FileHover = 14, | ||
| FileHoverCancelled = 15, | ||
| ScaleFactorChanged = 16, | ||
| ThemeChanged = 17, | ||
| Ime = 18, | ||
| Touch = 19, | ||
| } |
| // prettier-ignore | ||
| /* eslint-disable */ | ||
| // @ts-nocheck | ||
| /* auto-generated by NAPI-RS */ | ||
| const { readFileSync } = require('fs') | ||
| let nativeBinding = null; | ||
| const loadErrors = []; | ||
| const isMusl = () => { | ||
| let musl = false; | ||
| if (process.platform === 'linux') { | ||
| musl = isMuslFromFilesystem(); | ||
| if (musl === null) { | ||
| musl = isMuslFromReport(); | ||
| } | ||
| if (musl === null) { | ||
| musl = isMuslFromChildProcess(); | ||
| } | ||
| } | ||
| return musl; | ||
| }; | ||
| const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-'); | ||
| const isMuslFromFilesystem = () => { | ||
| try { | ||
| return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl'); | ||
| } catch { | ||
| return null; | ||
| } | ||
| }; | ||
| const isMuslFromReport = () => { | ||
| let report = null; | ||
| if (process.report && typeof process.report.getReport === 'function') { | ||
| process.report.excludeNetwork = true; | ||
| report = process.report.getReport(); | ||
| } | ||
| if (!report) { | ||
| return null; | ||
| } | ||
| if (report.header && report.header.glibcVersionRuntime) { | ||
| return false; | ||
| } | ||
| if (Array.isArray(report.sharedObjects)) { | ||
| if (report.sharedObjects.some(isFileMusl)) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| }; | ||
| const isMuslFromChildProcess = () => { | ||
| try { | ||
| return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl'); | ||
| } catch (e) { | ||
| // If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false | ||
| return false; | ||
| } | ||
| }; | ||
| function requireNative() { | ||
| if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) { | ||
| try { | ||
| return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH); | ||
| } catch (err) { | ||
| loadErrors.push(err); | ||
| } | ||
| } else if (process.platform === 'android') { | ||
| if (process.arch === 'arm64') { | ||
| try { | ||
| return require('./webview.android-arm64.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-android-arm64'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-android-arm64/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else if (process.arch === 'arm') { | ||
| try { | ||
| return require('./webview.android-arm-eabi.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-android-arm-eabi'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-android-arm-eabi/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else { | ||
| loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`)); | ||
| } | ||
| } else if (process.platform === 'win32') { | ||
| if (process.arch === 'x64') { | ||
| if ( | ||
| (process.config && process.config.variables && process.config.variables.shlib_suffix === 'dll.a') || | ||
| (process.config && process.config.variables && process.config.variables.node_target_type === 'shared_library') | ||
| ) { | ||
| try { | ||
| return require('./webview.win32-x64-gnu.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-win32-x64-gnu'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-win32-x64-gnu/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else { | ||
| try { | ||
| return require('./webview.win32-x64-msvc.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-win32-x64-msvc'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-win32-x64-msvc/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } | ||
| } else if (process.arch === 'ia32') { | ||
| try { | ||
| return require('./webview.win32-ia32-msvc.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-win32-ia32-msvc'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-win32-ia32-msvc/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else if (process.arch === 'arm64') { | ||
| try { | ||
| return require('./webview.win32-arm64-msvc.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-win32-arm64-msvc'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-win32-arm64-msvc/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else { | ||
| loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`)); | ||
| } | ||
| } else if (process.platform === 'darwin') { | ||
| try { | ||
| return require('./webview.darwin-universal.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-darwin-universal'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-darwin-universal/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| if (process.arch === 'x64') { | ||
| try { | ||
| return require('./webview.darwin-x64.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-darwin-x64'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-darwin-x64/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else if (process.arch === 'arm64') { | ||
| try { | ||
| return require('./webview.darwin-arm64.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-darwin-arm64'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-darwin-arm64/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else { | ||
| loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`)); | ||
| } | ||
| } else if (process.platform === 'freebsd') { | ||
| if (process.arch === 'x64') { | ||
| try { | ||
| return require('./webview.freebsd-x64.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-freebsd-x64'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-freebsd-x64/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else if (process.arch === 'arm64') { | ||
| try { | ||
| return require('./webview.freebsd-arm64.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-freebsd-arm64'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-freebsd-arm64/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else { | ||
| loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`)); | ||
| } | ||
| } else if (process.platform === 'linux') { | ||
| if (process.arch === 'x64') { | ||
| if (isMusl()) { | ||
| try { | ||
| return require('./webview.linux-x64-musl.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-linux-x64-musl'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-linux-x64-musl/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else { | ||
| try { | ||
| return require('./webview.linux-x64-gnu.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-linux-x64-gnu'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-linux-x64-gnu/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } | ||
| } else if (process.arch === 'arm64') { | ||
| if (isMusl()) { | ||
| try { | ||
| return require('./webview.linux-arm64-musl.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-linux-arm64-musl'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-linux-arm64-musl/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else { | ||
| try { | ||
| return require('./webview.linux-arm64-gnu.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-linux-arm64-gnu'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-linux-arm64-gnu/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } | ||
| } else if (process.arch === 'arm') { | ||
| if (isMusl()) { | ||
| try { | ||
| return require('./webview.linux-arm-musleabihf.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-linux-arm-musleabihf'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-linux-arm-musleabihf/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else { | ||
| try { | ||
| return require('./webview.linux-arm-gnueabihf.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-linux-arm-gnueabihf'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-linux-arm-gnueabihf/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } | ||
| } else if (process.arch === 'loong64') { | ||
| if (isMusl()) { | ||
| try { | ||
| return require('./webview.linux-loong64-musl.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-linux-loong64-musl'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-linux-loong64-musl/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else { | ||
| try { | ||
| return require('./webview.linux-loong64-gnu.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-linux-loong64-gnu'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-linux-loong64-gnu/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } | ||
| } else if (process.arch === 'riscv64') { | ||
| if (isMusl()) { | ||
| try { | ||
| return require('./webview.linux-riscv64-musl.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-linux-riscv64-musl'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-linux-riscv64-musl/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else { | ||
| try { | ||
| return require('./webview.linux-riscv64-gnu.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-linux-riscv64-gnu'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-linux-riscv64-gnu/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } | ||
| } else if (process.arch === 'ppc64') { | ||
| try { | ||
| return require('./webview.linux-ppc64-gnu.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-linux-ppc64-gnu'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-linux-ppc64-gnu/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else if (process.arch === 's390x') { | ||
| try { | ||
| return require('./webview.linux-s390x-gnu.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-linux-s390x-gnu'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-linux-s390x-gnu/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else { | ||
| loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`)); | ||
| } | ||
| } else if (process.platform === 'openharmony') { | ||
| if (process.arch === 'arm64') { | ||
| try { | ||
| return require('./webview.openharmony-arm64.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-openharmony-arm64'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-openharmony-arm64/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else if (process.arch === 'x64') { | ||
| try { | ||
| return require('./webview.openharmony-x64.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-openharmony-x64'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-openharmony-x64/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else if (process.arch === 'arm') { | ||
| try { | ||
| return require('./webview.openharmony-arm.node'); | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| try { | ||
| const binding = require('@webviewjs/webview-openharmony-arm'); | ||
| const bindingPackageVersion = require('@webviewjs/webview-openharmony-arm/package.json').version; | ||
| if ( | ||
| bindingPackageVersion !== '0.3.2' && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK && | ||
| process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0' | ||
| ) { | ||
| throw new Error( | ||
| `Native binding package version mismatch, expected 0.3.2 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`, | ||
| ); | ||
| } | ||
| return binding; | ||
| } catch (e) { | ||
| loadErrors.push(e); | ||
| } | ||
| } else { | ||
| loadErrors.push(new Error(`Unsupported architecture on OpenHarmony: ${process.arch}`)); | ||
| } | ||
| } else { | ||
| loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`)); | ||
| } | ||
| } | ||
| nativeBinding = requireNative(); | ||
| // NAPI_RS_FORCE_WASI is a tri-state flag: | ||
| // unset / any other value → native binding preferred, WASI is only a fallback | ||
| // 'true' → force WASI fallback even if native loaded | ||
| // 'error' → force WASI and throw if no WASI binding is found | ||
| // Treating any non-empty string as truthy (the historical behavior) meant | ||
| // NAPI_RS_FORCE_WASI=false, NAPI_RS_FORCE_WASI=0, etc. inadvertently triggered | ||
| // the WASI path, causing ENOENT for packages shipped without a .wasi.cjs file. | ||
| const forceWasi = process.env.NAPI_RS_FORCE_WASI === 'true' || process.env.NAPI_RS_FORCE_WASI === 'error'; | ||
| if (!nativeBinding || forceWasi) { | ||
| let wasiBinding = null; | ||
| let wasiBindingError = null; | ||
| try { | ||
| wasiBinding = require('./webview.wasi.cjs'); | ||
| nativeBinding = wasiBinding; | ||
| } catch (err) { | ||
| if (forceWasi) { | ||
| wasiBindingError = err; | ||
| } | ||
| } | ||
| if (!nativeBinding || forceWasi) { | ||
| try { | ||
| wasiBinding = require('@webviewjs/webview-wasm32-wasi'); | ||
| nativeBinding = wasiBinding; | ||
| } catch (err) { | ||
| if (forceWasi) { | ||
| if (!wasiBindingError) { | ||
| wasiBindingError = err; | ||
| } else { | ||
| wasiBindingError.cause = err; | ||
| } | ||
| loadErrors.push(err); | ||
| } | ||
| } | ||
| } | ||
| if (process.env.NAPI_RS_FORCE_WASI === 'error' && !wasiBinding) { | ||
| const error = new Error('WASI binding not found and NAPI_RS_FORCE_WASI is set to error'); | ||
| error.cause = wasiBindingError; | ||
| throw error; | ||
| } | ||
| } | ||
| if (!nativeBinding) { | ||
| if (loadErrors.length > 0) { | ||
| const error = new Error( | ||
| `Cannot find native binding. ` + | ||
| `npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). ` + | ||
| 'Please try `npm i` again after removing both package-lock.json and node_modules directory.', | ||
| ); | ||
| // assign instead of the `new Error(message, { cause })` options form, | ||
| // which Node < 16.9 silently ignores | ||
| error.cause = loadErrors.reduce((err, cur) => { | ||
| cur.cause = err; | ||
| return cur; | ||
| }); | ||
| throw error; | ||
| } | ||
| throw new Error(`Failed to load native binding`); | ||
| } | ||
| module.exports = nativeBinding; | ||
| module.exports.Application = nativeBinding.Application; | ||
| module.exports.BrowserWindow = nativeBinding.BrowserWindow; | ||
| module.exports.NativeNotification = nativeBinding.NativeNotification; | ||
| module.exports.JsNotification = nativeBinding.JsNotification; | ||
| module.exports.TrayIcon = nativeBinding.TrayIcon; | ||
| module.exports.JsTrayIcon = nativeBinding.JsTrayIcon; | ||
| module.exports.WebContext = nativeBinding.WebContext; | ||
| module.exports.JsWebContext = nativeBinding.JsWebContext; | ||
| module.exports.Webview = nativeBinding.Webview; | ||
| module.exports.JsWebview = nativeBinding.JsWebview; | ||
| module.exports.ControlFlow = nativeBinding.ControlFlow; | ||
| module.exports.JsControlFlow = nativeBinding.JsControlFlow; | ||
| module.exports.CursorType = nativeBinding.CursorType; | ||
| module.exports.FullscreenType = nativeBinding.FullscreenType; | ||
| module.exports.getWebviewVersion = nativeBinding.getWebviewVersion; | ||
| module.exports.IosValidOrientations = nativeBinding.IosValidOrientations; | ||
| module.exports.ProgressBarState = nativeBinding.ProgressBarState; | ||
| module.exports.JsProgressBarState = nativeBinding.JsProgressBarState; | ||
| module.exports.Theme = nativeBinding.Theme; | ||
| module.exports.VERSION = nativeBinding.VERSION; | ||
| module.exports.WebviewApplicationEvent = nativeBinding.WebviewApplicationEvent; | ||
| module.exports.WebviewEventType = nativeBinding.WebviewEventType; | ||
| module.exports.WindowCommand = nativeBinding.WindowCommand; | ||
| module.exports.WindowEventType = nativeBinding.WindowEventType; |
| MIT License | ||
| Copyright (c) 2024 Twilight | ||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
| { | ||
| "name": "@webviewjs/webview", | ||
| "version": "0.4.0", | ||
| "bin": { | ||
| "webview": "./cli/index.mjs", | ||
| "webviewjs": "./cli/index.mjs" | ||
| }, | ||
| "description": "Robust cross-platform webview library for Node.js written in Rust. It also works with deno and bun.", | ||
| "main": "./index.js", | ||
| "types": "./index.d.ts", | ||
| "repository": "git@github.com:webviewjs/webview.git", | ||
| "license": "MIT", | ||
| "type": "commonjs", | ||
| "keywords": [ | ||
| "webview", | ||
| "node", | ||
| "rust", | ||
| "cross-platform", | ||
| "gui" | ||
| ], | ||
| "files": [ | ||
| "cli", | ||
| "index.d.ts", | ||
| "index.js", | ||
| "js-bindings.js", | ||
| "js-bindings.d.ts", | ||
| "docs" | ||
| ], | ||
| "napi": { | ||
| "binaryName": "webview", | ||
| "targets": [ | ||
| "x86_64-pc-windows-msvc", | ||
| "i686-pc-windows-msvc", | ||
| "aarch64-pc-windows-msvc", | ||
| "x86_64-apple-darwin", | ||
| "aarch64-apple-darwin", | ||
| "x86_64-unknown-linux-gnu", | ||
| "i686-unknown-linux-gnu", | ||
| "aarch64-unknown-linux-gnu", | ||
| "armv7-unknown-linux-gnueabihf", | ||
| "aarch64-linux-android", | ||
| "armv7-linux-androideabi", | ||
| "x86_64-unknown-freebsd" | ||
| ], | ||
| "constEnum": false | ||
| }, | ||
| "engines": { | ||
| "node": ">= 24" | ||
| }, | ||
| "publishConfig": { | ||
| "registry": "https://registry.npmjs.org/", | ||
| "access": "public" | ||
| }, | ||
| "scripts": { | ||
| "artifacts": "napi artifacts", | ||
| "build": "node ./scripts/build.js --release", | ||
| "build:debug": "node ./scripts/build.js", | ||
| "clean": "napi clean && cargo clean", | ||
| "format": "run-p format:prettier format:rs", | ||
| "format:prettier": "prettier . -w", | ||
| "format:rs": "cargo fmt", | ||
| "lint": "oxlint .", | ||
| "lint:fix": "oxlint --fix .", | ||
| "test": "node --test __test__/*.test.*", | ||
| "test:watch": "node --test --watch __test__/*.test.*", | ||
| "prepublishOnly": "napi prepublish -t npm", | ||
| "version": "napi version", | ||
| "check": "cargo check", | ||
| "clippy": "cargo clippy -- -D warnings", | ||
| "docs": "cargo doc --no-deps --open", | ||
| "update": "bun update", | ||
| "setup": "bash ./scripts/install-linux-deps.sh" | ||
| }, | ||
| "devDependencies": { | ||
| "@napi-rs/cli": "^3.5.1", | ||
| "@swc-node/register": "^1.11.1", | ||
| "@swc/core": "^1.15.8", | ||
| "@types/node": "^26.0.0", | ||
| "ava": "^6.4.1", | ||
| "chalk": "^5.6.2", | ||
| "hono": "^4.12.27", | ||
| "husky": "^9.1.7", | ||
| "lint-staged": "^15.5.2", | ||
| "nodeia": "^0.0.1", | ||
| "npm-run-all2": "^7.0.2", | ||
| "oxlint": "^0.13.2", | ||
| "prettier": "^3.7.4", | ||
| "tinybench": "^2.9.0", | ||
| "typescript": "^5.9.3" | ||
| }, | ||
| "lint-staged": { | ||
| "*.@(js|ts|tsx)": [ | ||
| "oxlint --fix" | ||
| ], | ||
| "*.@(js|ts|tsx|yml|yaml|md|json)": [ | ||
| "prettier --write" | ||
| ] | ||
| }, | ||
| "prettier": { | ||
| "printWidth": 120, | ||
| "semi": true, | ||
| "trailingComma": "all", | ||
| "singleQuote": true, | ||
| "arrowParens": "always", | ||
| "endOfLine": "lf", | ||
| "tabWidth": 2, | ||
| "useTabs": false | ||
| }, | ||
| "packageManager": "bun@1.3.14", | ||
| "optionalDependencies": { | ||
| "@webviewjs/webview-win32-x64-msvc": "0.4.0", | ||
| "@webviewjs/webview-win32-ia32-msvc": "0.4.0", | ||
| "@webviewjs/webview-win32-arm64-msvc": "0.4.0", | ||
| "@webviewjs/webview-darwin-x64": "0.4.0", | ||
| "@webviewjs/webview-darwin-arm64": "0.4.0", | ||
| "@webviewjs/webview-linux-x64-gnu": "0.4.0", | ||
| "@webviewjs/webview-linux-ia32-gnu": "0.4.0", | ||
| "@webviewjs/webview-linux-arm64-gnu": "0.4.0", | ||
| "@webviewjs/webview-linux-arm-gnueabihf": "0.4.0", | ||
| "@webviewjs/webview-android-arm64": "0.4.0", | ||
| "@webviewjs/webview-android-arm-eabi": "0.4.0", | ||
| "@webviewjs/webview-freebsd-x64": "0.4.0" | ||
| } | ||
| } |
| # `@webviewjs/webview` | ||
|  | ||
| Robust cross-platform webview library for Node.js written in Rust. It is a native binding to [tao](https://github.com/tauri-apps/tao) and [wry](https://github.com/tauri-apps/wry) allowing you to easily manage cross platform windowing and webview. | ||
| ## Highlights | ||
| - Promise-based application readiness with optional automatic event pumping. | ||
| - Non-blocking application pumping, so ordinary Node timers and I/O continue running. | ||
| - Browser windows, menus, dialogs, cookies, DevTools, and window controls. | ||
| - Typed EventEmitter APIs for applications, windows, webviews, and system tray icons. | ||
| - Shared browser contexts for profile, cookie, cache, and storage isolation. | ||
| - Cross-platform system tray icons with menus and runtime updates. | ||
| - Native desktop notifications with a browser-familiar API. | ||
| - Native Windows, macOS, X11, Wayland, iOS, and Android window extensions. | ||
| - IPC through `window.ipc.postMessage()`, with an optional alias such as `window.bindings`. | ||
| - Fetch-compatible asynchronous custom protocols, including Hono routing without an HTTP server. | ||
| - Promise-based `webview.expose()` namespaces for page-to-Node calls. | ||
|  | ||
| > [!NOTE] | ||
| > This library is meant to be a lightweight system webview binding for JavaScript. It does not aim to be a full-featured framework like Electron or Tauri. Please report any issues you find, and consider contributing to the project if you need additional features. | ||
| See the [full documentation](./) for API references, guides, platform | ||
| notes, and runnable examples. | ||
| # Documentation | ||
| ## Getting started | ||
| | | | | ||
| | ---------------------------------------------- | ------------------------------- | | ||
| | [Installation](./getting-started/installation) | System requirements and setup | | ||
| | [Quick Start](./getting-started/quick-start) | Your first window in minutes | | ||
| | [Event Loop](./getting-started/event-loop) | How the non-blocking pump works | | ||
| ## API reference | ||
| | | | | ||
| | ------------------------------------- | ------------------------------------------------------ | | ||
| | [Application](./api/application) | Root object — event loop, windows, menus | | ||
| | [BrowserWindow](./api/browser-window) | OS window, size, position, cursor, decorations | | ||
| | [Webview](./api/webview) | Embedded browser — navigation, cookies, script, bounds | | ||
| | [WebContext](./api/web-context) | Shared browser data, profiles, and automation | | ||
| | [System Tray](./api/tray) | Tray icons, menus, updates, and pointer events | | ||
| | [Notification](./api/notification) | Native desktop notifications and lifecycle events | | ||
| | [Menu](./api/menu) | Native menu bar construction | | ||
| | [Types](./api/types) | Shared interfaces and enums | | ||
| ## Guides | ||
| | | | | ||
| | ----------------------------------------------------- | ----------------------------------------------- | | ||
| | [Building Executables](./guides/building-executables) | Compile to `.exe` / binary with node, deno, bun | | ||
| | [IPC Messaging](./guides/ipc-messaging) | Page ↔ Node communication | | ||
| | [Menus](./guides/menus) | Building menu bars with roles and accelerators | | ||
| | [Multiple Windows](./guides/multiple-windows) | Managing several windows | | ||
| | [Cookies & Storage](./guides/cookies-and-storage) | Reading, writing, and clearing cookies | | ||
| | [Custom Protocols](./guides/custom-protocols) | Serving local content to the webview | | ||
| ## Platform notes | ||
| | | | | ||
| | ----------------------------- | ----------------------------------------- | | ||
| | [Windows](./platform/windows) | WebView2, taskbar, DPI | | ||
| | [macOS](./platform/macos) | WebKit, main-thread requirement, app menu | | ||
| | [Linux](./platform/linux) | WebKitGTK, Wayland/X11, menu limitations | | ||
| | [iOS](./platform/ios) | Orientation, status bar, and gestures | | ||
| | [Android](./platform/android) | Content rectangle and configuration | | ||
| # Installation | ||
| ```bash | ||
| npm install @webviewjs/webview | ||
| ``` | ||
| # Supported platforms | ||
| | Platform | OS | Arch | Supported | | ||
| | ----------------------------- | ------- | ----- | ----------------- | | ||
| | x86_64-pc-windows-msvc | Windows | x64 | ✅ | | ||
| | i686-pc-windows-msvc | Windows | x86 | ✅ | | ||
| | aarch64-pc-windows-msvc | Windows | arm64 | ✅ | | ||
| | x86_64-apple-darwin | macOS | x64 | ✅ | | ||
| | aarch64-apple-darwin | macOS | arm64 | ✅ | | ||
| | x86_64-unknown-linux-gnu | Linux | x64 | ✅ | | ||
| | aarch64-unknown-linux-gnu | Linux | arm64 | ✅ | | ||
| | armv7-unknown-linux-gnueabihf | Linux | armv7 | ✅ | | ||
| | i686-unknown-linux-gnu | Linux | x86 | ⚠️ (no CI) | | ||
| | aarch64-linux-android | Android | arm64 | ⚠️ (experimental) | | ||
| | armv7-linux-androideabi | Android | armv7 | ⚠️ (experimental) | | ||
| | x86_64-unknown-freebsd | FreeBSD | x64 | ⚠️ (no CI) | | ||
| # Examples | ||
| ## Load external url | ||
| ```js | ||
| import { Application } from '@webviewjs/webview'; | ||
| // or | ||
| const { Application } = require('@webviewjs/webview'); | ||
| const app = new Application(); | ||
| let mainWindow = null; | ||
| let mainWebview = null; | ||
| app.whenReady().then(() => { | ||
| mainWindow = app.createBrowserWindow(); | ||
| mainWebview = mainWindow.createWebview({ url: 'https://nodejs.org' }); | ||
| }); | ||
| ``` | ||
| ## Event pumping | ||
| `app.whenReady()` starts the non-blocking event pump by default: | ||
| ```js | ||
| await app.whenReady({ interval: 16, ref: true }); | ||
| ``` | ||
| For manual startup, disable auto-run: | ||
| ```js | ||
| const ready = app.whenReady({ autoRun: false }); | ||
| app.run({ interval: 16, ref: true }); | ||
| await ready; | ||
| ``` | ||
| `interval` defaults to `16` milliseconds and `ref` defaults to `true`. Use `app.pumpEvents()` for manual pumping. | ||
| ## System tray | ||
| Keep a strong JavaScript reference when you need to call tray methods or keep | ||
| its listeners reachable: | ||
| ```js | ||
| let tray = null; | ||
| app.whenReady().then(() => { | ||
| tray = app.createTrayIcon({ | ||
| id: 'main', | ||
| icon: { data: rgba, width: 16, height: 16 }, | ||
| tooltip: 'My application', | ||
| menu: { items: [{ id: 'quit', label: 'Quit' }] }, | ||
| }); | ||
| tray.on('click', (event) => console.log(event)); | ||
| }); | ||
| ``` | ||
| See the [system tray reference](./api/tray) and | ||
| [runnable tray example](./examples/tray.mjs). | ||
| ## Notifications | ||
| ```js | ||
| import { Notification } from '@webviewjs/webview'; | ||
| const notification = new Notification('Build complete', { | ||
| body: 'The release executable is ready.', | ||
| }); | ||
| notification.on('click', () => console.log('notification clicked')); | ||
| notification.on('error', ({ error }) => console.error(error)); | ||
| ``` | ||
| Notification permission is always `"granted"` for native applications. See the | ||
| [notification reference](./api/notification) and | ||
| [runnable notification example](./examples/notification.mjs). | ||
| ## IPC and exposed functions | ||
| The webview page can send messages to Node through `window.ipc.postMessage()`: | ||
| ```js | ||
| const webview = window.createWebview({ ipcName: 'bindings' }); | ||
| webview.onIpcMessage((message) => console.log(message.body.toString())); | ||
| ``` | ||
| `ipcName` adds an alias, so the page can use `window.bindings.postMessage(...)`; `window.ipc` remains available. | ||
| For typed request/response style calls, expose a namespace: | ||
| ```js | ||
| webview.expose('native', { | ||
| version: '0.1.4', | ||
| readConfig: async () => JSON.parse(await readFile('./config.json', 'utf8')), | ||
| }); | ||
| ``` | ||
| In the page: | ||
| ```js | ||
| console.log(window.native.version); | ||
| const config = await window.native.readConfig(); | ||
| ``` | ||
| Every exposed function returns a Promise in the page. Values, arguments, and results must be JSON-serializable. Violations use `SerializationError`. | ||
| ## Asynchronous custom protocols | ||
| Register a protocol before creating its webview: | ||
| ```js | ||
| window.registerProtocol('app', async (request) => { | ||
| const filePath = join(process.cwd(), 'dist', new URL(request.url).pathname); | ||
| try { | ||
| return new Response(await readFile(filePath), { | ||
| headers: { 'Content-Type': 'text/html; charset=utf-8' }, | ||
| }); | ||
| } catch { | ||
| return new Response('Not found', { | ||
| status: 404, | ||
| headers: { 'Content-Type': 'text/plain; charset=utf-8' }, | ||
| }); | ||
| } | ||
| }); | ||
| window.createWebview({ url: 'app://localhost/index.html' }); | ||
| ``` | ||
| See [Custom Protocols](docs/guides/custom-protocols), [IPC](docs/guides/ipc-messaging), and the runnable [custom protocol](examples/custom-protocol.mjs) and [expose](examples/expose.mjs) examples. | ||
| ## Menu System | ||
| WebviewJS provides a cross-platform menu system that works on macOS, Windows, and Linux. | ||
| ### Basic Menu Setup | ||
| ```js | ||
| import { Application } from '@webviewjs/webview'; | ||
| const app = new Application(); | ||
| // Set global application menu | ||
| app.setMenu({ | ||
| items: [ | ||
| { | ||
| label: 'File', | ||
| submenu: { | ||
| items: [ | ||
| { id: 'new', label: 'New', accelerator: 'CmdOrCtrl+N' }, | ||
| { id: 'open', label: 'Open', accelerator: 'CmdOrCtrl+O' }, | ||
| { role: 'separator' }, | ||
| { id: 'quit', label: 'Quit', accelerator: 'CmdOrCtrl+Q' }, | ||
| ], | ||
| }, | ||
| }, | ||
| { | ||
| label: 'Edit', | ||
| submenu: { | ||
| items: [{ role: 'copy' }, { role: 'paste' }, { role: 'cut' }, { role: 'selectall' }], | ||
| }, | ||
| }, | ||
| ], | ||
| }); | ||
| const window = app.createBrowserWindow(); | ||
| const webview = window.createWebview({ url: 'https://nodejs.org' }); | ||
| app.run(); | ||
| ``` | ||
| ### Menu Event Handling | ||
| ```js | ||
| import { Application } from '@webviewjs/webview'; | ||
| const app = new Application(); | ||
| // Handle menu events | ||
| app.on('custom-menu-click', ({ customMenuEvent: menuEvent }) => { | ||
| console.log(`Menu item clicked: ${menuEvent.id}`); | ||
| console.log(`From window: ${menuEvent.windowId}`); | ||
| // Handle specific menu items | ||
| switch (menuEvent.id) { | ||
| case 'new': | ||
| console.log('Creating new document...'); | ||
| break; | ||
| case 'open': | ||
| console.log('Opening file...'); | ||
| break; | ||
| case 'quit': | ||
| app.exit(); | ||
| break; | ||
| } | ||
| }); | ||
| // Set up menu... | ||
| app.setMenu({ | ||
| /* ... */ | ||
| }); | ||
| ``` | ||
| ### Window-Specific Menus | ||
| ```js | ||
| const app = new Application(); | ||
| // Create window with custom menu | ||
| const window = app.createBrowserWindow({ | ||
| title: 'Custom Window', | ||
| menu: { | ||
| items: [ | ||
| { | ||
| id: 'window-action', | ||
| label: 'Window Action', | ||
| accelerator: 'Ctrl+W', | ||
| }, | ||
| ], | ||
| }, | ||
| }); | ||
| // Or check if window has a menu | ||
| if (window.hasMenu()) { | ||
| console.log('This window has a menu'); | ||
| } | ||
| ``` | ||
| ### Menu Item Options | ||
| - **`id`**: Unique identifier for the menu item (used in events) | ||
| - **`label`**: Display text for the menu item | ||
| - **`enabled`**: Whether the item is clickable (default: true) | ||
| - **`accelerator`**: Keyboard shortcut (e.g., "CmdOrCtrl+N", "Alt+F4") | ||
| - **`submenu`**: Nested menu items | ||
| - **`role`**: Predefined menu items with built-in behavior | ||
| ### Predefined Menu Roles | ||
| - **`"copy"`**: Standard copy action | ||
| - **`"paste"`**: Standard paste action | ||
| - **`"cut"`**: Standard cut action | ||
| - **`"selectall"`**: Select all text action | ||
| - **`"separator"`**: Visual separator line | ||
| ## IPC | ||
| ```js | ||
| const app = new Application(); | ||
| const window = app.createBrowserWindow(); | ||
| const webview = window.createWebview({ | ||
| html: `<!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>Webview</title> | ||
| </head> | ||
| <body> | ||
| <h1 id="output">Hello world!</h1> | ||
| <button id="btn">Click me!</button> | ||
| <script> | ||
| btn.onclick = function send() { | ||
| window.ipc.postMessage('Hello from webview'); | ||
| } | ||
| </script> | ||
| </body> | ||
| </html> | ||
| `, | ||
| preload: `window.onIpcMessage = function(data) { | ||
| const output = document.getElementById('output'); | ||
| output.innerText = \`Server Sent A Message: \${data}\`; | ||
| }`, | ||
| }); | ||
| if (!webview.isDevtoolsOpen()) webview.openDevtools(); | ||
| webview.onIpcMessage((data) => { | ||
| const reply = `You sent ${data.body.toString('utf-8')}`; | ||
| webview.evaluateScript(`onIpcMessage("${reply}")`); | ||
| }); | ||
| app.run(); | ||
| ``` | ||
| ## Closing the Application | ||
| You can close the application, windows, and webviews gracefully to ensure all resources (including temporary folders) are cleaned up properly. | ||
| ```js | ||
| const app = new Application(); | ||
| const window = app.createBrowserWindow(); | ||
| const webview = window.createWebview({ url: 'https://nodejs.org' }); | ||
| app.on('application-close-requested', () => { | ||
| console.log('Application is closing, cleaning up resources...'); | ||
| }); | ||
| app.on('window-close-requested', () => { | ||
| console.log('Window close requested'); | ||
| }); | ||
| // Close the application gracefully (cleans up temp folders) | ||
| app.exit(); | ||
| // Or hide/show the window | ||
| window.hide(); // Hide the window | ||
| window.show(); // Show the window again | ||
| // Or reload the webview | ||
| webview.reload(); | ||
| ``` | ||
| For more details on closing applications and cleaning up resources, see the [Closing Guide](./CLOSING_GUIDE). | ||
| ## Keep strong references | ||
| Retain `BrowserWindow`, `Webview`, `WebContext`, and `TrayIcon` wrappers for as | ||
| long as you need to call their methods or retain their JavaScript listeners. | ||
| Avoid discarded temporary handles: | ||
| ```js | ||
| const windows = []; | ||
| app.whenReady().then(() => { | ||
| const window = app.createBrowserWindow(); | ||
| const webview = window.createWebview({ url: 'https://example.com' }); | ||
| windows.push({ window, webview }); | ||
| }); | ||
| ``` | ||
| The root `Application` owns native resources created through it. `app.exit()`, | ||
| `app[Symbol.dispose]()`, and application garbage collection dispose those | ||
| resources in shutdown order. Retained wrappers then report `isDisposed() === | ||
| true`, and method calls fail with a disposed error. Individual windows, | ||
| webviews, contexts, and tray icons also support `dispose()` and | ||
| `Symbol.dispose`. | ||
| Check out [examples](./examples) directory for more examples: | ||
| - **[menu-system.mjs](./examples/menu-system.mjs)** - Comprehensive menu system demonstration with all features | ||
| - **[window-menus.mjs](./examples/window-menus.mjs)** - Window-specific vs global menu examples | ||
| - **[http/](./examples/http/)** - Serving content from a web server to webview | ||
| - **[transparent.mjs](./examples/transparent.mjs)** - Transparent window example | ||
| - **[close-example.mjs](./examples/close-example.mjs)** - Graceful application closing | ||
| Run any example with: `node examples/menu-system.mjs` (after building the project) | ||
| # Building executables | ||
| > [!WARNING] | ||
| > The CLI feature is very experimental and may not work as expected. Please report any issues you find. | ||
| The `webview` CLI compiles your app into a single self-contained executable. The runtime is auto-detected (`Bun` → bun, `Deno` → deno, otherwise Node.js), or you can override it: | ||
| ```bash | ||
| # Auto-detected runtime | ||
| webview --build --input ./path/to/your/script.js --output ./dist --name my-app | ||
| # Explicit runtime | ||
| webview --build --runtime node --input ./src/index.js --name my-app | ||
| webview --build --runtime deno --input ./src/index.ts --name my-app | ||
| webview --build --runtime bun --input ./src/index.ts --name my-app | ||
| ``` | ||
| | Flag | Default | Description | | ||
| | -------------------- | ------------- | -------------------------- | | ||
| | `--runtime` / `-R` | auto-detected | `node`, `deno`, or `bun` | | ||
| | `--input` / `-i` | `./index.js` | Entry file | | ||
| | `--output` / `-o` | `./dist` | Output directory | | ||
| | `--name` / `-n` | `webviewjs` | Executable name | | ||
| | `--resources` / `-r` | — | JSON asset map (node only) | | ||
| For the full compilation guide including cross-compilation and code signing, see [Building Executables](./guides/building-executables). | ||
| # Development | ||
| ## Prerequisites | ||
| - [Bun](https://bun.sh/) >= 1.3.0 | ||
| - [Rust](https://www.rust-lang.org/) stable toolchain | ||
| - [Node.js](https://nodejs.org/) >= 24 (for testing) | ||
| ## Setup | ||
| ```bash | ||
| bun install | ||
| ``` | ||
| ## Build | ||
| ```bash | ||
| bun run build | ||
| ``` |
+1
-1
@@ -9,3 +9,3 @@ #!/usr/bin/env node | ||
| var Ve=Object.create;var B=Object.defineProperty;var Ue=Object.getOwnPropertyDescriptor;var je=Object.getOwnPropertyNames;var We=Object.getPrototypeOf,qe=Object.prototype.hasOwnProperty;var M=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,r)=>(typeof require<"u"?require:t)[r]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var x=(e,t)=>()=>{try{return t||e((t={exports:{}}).exports,t),t.exports}catch(r){throw t=0,r}};var Ge=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of je(t))!qe.call(e,o)&&o!==r&&B(e,o,{get:()=>t[o],enumerable:!(n=Ue(t,o))||n.enumerable});return e};var Ke=(e,t,r)=>(r=e!=null?Ve(We(e)):{},Ge(t||!e||!e.__esModule?B(r,"default",{value:e,enumerable:!0}):r,e));var X=x((Jt,z)=>{"use strict";var Y=()=>process.platform==="linux",C=null,Ze=()=>{if(!C)if(Y()&&process.report){let e=process.report.excludeNetwork;process.report.excludeNetwork=!0,C=process.report.getReport(),process.report.excludeNetwork=e}else C={};return C};z.exports={isLinux:Y,getReport:Ze}});var Z=x((Yt,Q)=>{"use strict";var y=M("fs"),et="/usr/bin/ldd",tt="/proc/self/exe",L=2048,rt=e=>{let t=y.openSync(e,"r"),r=Buffer.alloc(L),n=y.readSync(t,r,0,L,0);return y.close(t,()=>{}),r.subarray(0,n)},nt=e=>new Promise((t,r)=>{y.open(e,"r",(n,o)=>{if(n)r(n);else{let i=Buffer.alloc(L);y.read(o,i,0,L,0,(s,a)=>{t(i.subarray(0,a)),y.close(o,()=>{})})}})});Q.exports={LDD_PATH:et,SELF_PATH:tt,readFileSync:rt,readFile:nt}});var te=x((zt,ee)=>{"use strict";var ot=e=>{if(e.length<64||e.readUInt32BE(0)!==2135247942||e.readUInt8(4)!==2||e.readUInt8(5)!==1)return null;let t=e.readUInt32LE(32),r=e.readUInt16LE(54),n=e.readUInt16LE(56);for(let o=0;o<n;o++){let i=t+o*r;if(e.readUInt32LE(i)===3){let a=e.readUInt32LE(i+8),h=e.readUInt32LE(i+32);return e.subarray(a,a+h).toString().replace(/\0.*$/g,"")}}return null};ee.exports={interpreterPath:ot}});var xe=x((Xt,ve)=>{"use strict";var ne=M("child_process"),{isLinux:b,getReport:oe}=X(),{LDD_PATH:S,SELF_PATH:ie,readFile:w,readFileSync:I}=Z(),{interpreterPath:se}=te(),u,f,d,ae="getconf GNU_LIBC_VERSION 2>&1 || true; ldd --version 2>&1 || true",m="",ce=()=>m||new Promise(e=>{ne.exec(ae,(t,r)=>{m=t?" ":r,e(m)})}),le=()=>{if(!m)try{m=ne.execSync(ae,{encoding:"utf8"})}catch{m=" "}return m},p="glibc",ue=/LIBC[a-z0-9 \-).]*?(\d+\.\d+)/i,g="musl",it=e=>e.includes("libc.musl-")||e.includes("ld-musl-"),fe=()=>{let e=oe();return e.header&&e.header.glibcVersionRuntime?p:Array.isArray(e.sharedObjects)&&e.sharedObjects.some(it)?g:null},de=e=>{let[t,r]=e.split(/[\r\n]+/);return t&&t.includes(p)?p:r&&r.includes(g)?g:null},pe=e=>{if(e){if(e.includes("/ld-musl-"))return g;if(e.includes("/ld-linux-"))return p}return null},me=e=>(e=e.toString(),e.includes("musl")?g:e.includes("GNU C Library")?p:null),st=async()=>{if(f!==void 0)return f;f=null;try{let e=await w(S);f=me(e)}catch{}return f},at=()=>{if(f!==void 0)return f;f=null;try{let e=I(S);f=me(e)}catch{}return f},ct=async()=>{if(u!==void 0)return u;u=null;try{let e=await w(ie),t=se(e);u=pe(t)}catch{}return u},lt=()=>{if(u!==void 0)return u;u=null;try{let e=I(ie),t=se(e);u=pe(t)}catch{}return u},ge=async()=>{let e=null;if(b()&&(e=await ct(),!e&&(e=await st(),e||(e=fe()),!e))){let t=await ce();e=de(t)}return e},he=()=>{let e=null;if(b()&&(e=lt(),!e&&(e=at(),e||(e=fe()),!e))){let t=le();e=de(t)}return e},ut=async()=>b()&&await ge()!==p,ft=()=>b()&&he()!==p,dt=async()=>{if(d!==void 0)return d;d=null;try{let t=(await w(S)).match(ue);t&&(d=t[1])}catch{}return d},pt=()=>{if(d!==void 0)return d;d=null;try{let t=I(S).match(ue);t&&(d=t[1])}catch{}return d},ye=()=>{let e=oe();return e.header&&e.header.glibcVersionRuntime?e.header.glibcVersionRuntime:null},re=e=>e.trim().split(/\s+/)[1],be=e=>{let[t,r,n]=e.split(/[\r\n]+/);return t&&t.includes(p)?re(t):r&&n&&r.includes(g)?re(n):null},mt=async()=>{let e=null;if(b()&&(e=await dt(),e||(e=ye()),!e)){let t=await ce();e=be(t)}return e},gt=()=>{let e=null;if(b()&&(e=pt(),e||(e=ye()),!e)){let t=le();e=be(t)}return e};ve.exports={GLIBC:p,MUSL:g,family:ge,familySync:he,isNonGlibcLinux:ut,isNonGlibcLinuxSync:ft,version:mt,versionSync:gt}});import Dt from"node:module";import{dirname as $t,join as Bt}from"node:path";import*as Me from"node:sea";import{fileURLToPath as Mt,pathToFileURL as k}from"node:url";import{basename as ht,join as A}from"node:path";var V="0.0.1";import{readdir as Je,access as Ye,constants as ze}from"node:fs/promises";import{join as c,basename as U}from"node:path";import{homedir as E}from"node:os";function W(){return process.env.XDG_CACHE_HOME||c(E(),".cache")}function q(){if(process.argv.includes("--no-auto-update")||process.argv.includes("--prefer-version"))return!1;let e=process.env.COPILOT_AUTO_UPDATE;return!(e&&e.toLowerCase()==="false")}function G(){let e=process.argv.indexOf("--prefer-version");if(!(e===-1||e+1>=process.argv.length))return process.argv[e+1]}function Xe(){if(process.platform==="darwin")return c(E(),"Library","Caches","copilot");if(process.platform==="win32"){let e=process.env.LOCALAPPDATA||c(E(),".cache");return c(e,"copilot")}return c(W(),"copilot")}function K(){let e=[];return process.env.COPILOT_CACHE_HOME&&e.push(c(process.env.COPILOT_CACHE_HOME,"pkg")),e.push(c(Xe(),"pkg")),e.push(c(W(),"copilot","pkg")),process.env.COPILOT_HOME&&e.push(c(process.env.COPILOT_HOME,"pkg")),e.push(c(E(),".copilot","pkg")),[...new Set(e)]}function j(e){let t=e.match(/^(\d+)\.(\d+)\.(\d+)/);if(t)return[Number(t[1]),Number(t[2]),Number(t[3])]}function Qe(e,t){let r=j(e),n=j(t);if(!r&&!n)return 0;if(!r)return-1;if(!n)return 1;for(let s=0;s<3;s++)if(r[s]!==n[s])return r[s]-n[s];let o=e.includes("-"),i=t.includes("-");return o!==i?o?-1:1:e.localeCompare(t)}async function J(e,...t){let r=[];for(let n of t){let o;try{o=await Je(n)}catch{continue}for(let i of o){let s=c(n,i);try{await Ye(c(s,e),ze.R_OK),r.push(s)}catch{continue}}}return r.sort((n,o)=>{let i=Qe(U(o),U(n));return i!==0?i:n.localeCompare(o)}),r}import{join as Ce}from"node:path";var P=Ke(xe(),1);function T(e={}){return(e.platform??process.platform)!=="linux"?"gnu":(e.detectLibcFamily??P.familySync)()===P.MUSL?"musl":"gnu"}function N(e=process.platform,t){let r=t??(e==="linux"?T():"gnu");return e==="linux"&&r==="musl"?"linuxmusl":e}function Ee(e=process.platform,t,r=process.arch){return`${N(e,t)}-${r}`}function Le(){let e=Ee();return K().flatMap(t=>[Ce(t,"universal"),Ce(t,e)])}function yt(){return process.env.COPILOT_CLI_VERSION?process.env.COPILOT_CLI_VERSION:"1.0.71-1"}async function Se(e,t){let r=A(e,"app.js"),n=yt()===V,o=G();if(t&&(o||q()&&!n)){let i=Le(),s=await J("app.js",...i);if(o){let a=s.find(h=>ht(h)===o);a?r=A(a,"app.js"):process.stderr.write(`Warning: preferred version ${o} not found in package cache, using built-in version | ||
| var Ve=Object.create;var B=Object.defineProperty;var Ue=Object.getOwnPropertyDescriptor;var je=Object.getOwnPropertyNames;var We=Object.getPrototypeOf,qe=Object.prototype.hasOwnProperty;var M=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(t,r)=>(typeof require<"u"?require:t)[r]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var x=(e,t)=>()=>{try{return t||e((t={exports:{}}).exports,t),t.exports}catch(r){throw t=0,r}};var Ge=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of je(t))!qe.call(e,o)&&o!==r&&B(e,o,{get:()=>t[o],enumerable:!(n=Ue(t,o))||n.enumerable});return e};var Ke=(e,t,r)=>(r=e!=null?Ve(We(e)):{},Ge(t||!e||!e.__esModule?B(r,"default",{value:e,enumerable:!0}):r,e));var X=x((Jt,z)=>{"use strict";var Y=()=>process.platform==="linux",C=null,Ze=()=>{if(!C)if(Y()&&process.report){let e=process.report.excludeNetwork;process.report.excludeNetwork=!0,C=process.report.getReport(),process.report.excludeNetwork=e}else C={};return C};z.exports={isLinux:Y,getReport:Ze}});var Z=x((Yt,Q)=>{"use strict";var y=M("fs"),et="/usr/bin/ldd",tt="/proc/self/exe",L=2048,rt=e=>{let t=y.openSync(e,"r"),r=Buffer.alloc(L),n=y.readSync(t,r,0,L,0);return y.close(t,()=>{}),r.subarray(0,n)},nt=e=>new Promise((t,r)=>{y.open(e,"r",(n,o)=>{if(n)r(n);else{let i=Buffer.alloc(L);y.read(o,i,0,L,0,(s,a)=>{t(i.subarray(0,a)),y.close(o,()=>{})})}})});Q.exports={LDD_PATH:et,SELF_PATH:tt,readFileSync:rt,readFile:nt}});var te=x((zt,ee)=>{"use strict";var ot=e=>{if(e.length<64||e.readUInt32BE(0)!==2135247942||e.readUInt8(4)!==2||e.readUInt8(5)!==1)return null;let t=e.readUInt32LE(32),r=e.readUInt16LE(54),n=e.readUInt16LE(56);for(let o=0;o<n;o++){let i=t+o*r;if(e.readUInt32LE(i)===3){let a=e.readUInt32LE(i+8),h=e.readUInt32LE(i+32);return e.subarray(a,a+h).toString().replace(/\0.*$/g,"")}}return null};ee.exports={interpreterPath:ot}});var xe=x((Xt,ve)=>{"use strict";var ne=M("child_process"),{isLinux:b,getReport:oe}=X(),{LDD_PATH:S,SELF_PATH:ie,readFile:w,readFileSync:I}=Z(),{interpreterPath:se}=te(),u,f,d,ae="getconf GNU_LIBC_VERSION 2>&1 || true; ldd --version 2>&1 || true",m="",ce=()=>m||new Promise(e=>{ne.exec(ae,(t,r)=>{m=t?" ":r,e(m)})}),le=()=>{if(!m)try{m=ne.execSync(ae,{encoding:"utf8"})}catch{m=" "}return m},p="glibc",ue=/LIBC[a-z0-9 \-).]*?(\d+\.\d+)/i,g="musl",it=e=>e.includes("libc.musl-")||e.includes("ld-musl-"),fe=()=>{let e=oe();return e.header&&e.header.glibcVersionRuntime?p:Array.isArray(e.sharedObjects)&&e.sharedObjects.some(it)?g:null},de=e=>{let[t,r]=e.split(/[\r\n]+/);return t&&t.includes(p)?p:r&&r.includes(g)?g:null},pe=e=>{if(e){if(e.includes("/ld-musl-"))return g;if(e.includes("/ld-linux-"))return p}return null},me=e=>(e=e.toString(),e.includes("musl")?g:e.includes("GNU C Library")?p:null),st=async()=>{if(f!==void 0)return f;f=null;try{let e=await w(S);f=me(e)}catch{}return f},at=()=>{if(f!==void 0)return f;f=null;try{let e=I(S);f=me(e)}catch{}return f},ct=async()=>{if(u!==void 0)return u;u=null;try{let e=await w(ie),t=se(e);u=pe(t)}catch{}return u},lt=()=>{if(u!==void 0)return u;u=null;try{let e=I(ie),t=se(e);u=pe(t)}catch{}return u},ge=async()=>{let e=null;if(b()&&(e=await ct(),!e&&(e=await st(),e||(e=fe()),!e))){let t=await ce();e=de(t)}return e},he=()=>{let e=null;if(b()&&(e=lt(),!e&&(e=at(),e||(e=fe()),!e))){let t=le();e=de(t)}return e},ut=async()=>b()&&await ge()!==p,ft=()=>b()&&he()!==p,dt=async()=>{if(d!==void 0)return d;d=null;try{let t=(await w(S)).match(ue);t&&(d=t[1])}catch{}return d},pt=()=>{if(d!==void 0)return d;d=null;try{let t=I(S).match(ue);t&&(d=t[1])}catch{}return d},ye=()=>{let e=oe();return e.header&&e.header.glibcVersionRuntime?e.header.glibcVersionRuntime:null},re=e=>e.trim().split(/\s+/)[1],be=e=>{let[t,r,n]=e.split(/[\r\n]+/);return t&&t.includes(p)?re(t):r&&n&&r.includes(g)?re(n):null},mt=async()=>{let e=null;if(b()&&(e=await dt(),e||(e=ye()),!e)){let t=await ce();e=be(t)}return e},gt=()=>{let e=null;if(b()&&(e=pt(),e||(e=ye()),!e)){let t=le();e=be(t)}return e};ve.exports={GLIBC:p,MUSL:g,family:ge,familySync:he,isNonGlibcLinux:ut,isNonGlibcLinuxSync:ft,version:mt,versionSync:gt}});import Dt from"node:module";import{dirname as $t,join as Bt}from"node:path";import*as Me from"node:sea";import{fileURLToPath as Mt,pathToFileURL as k}from"node:url";import{basename as ht,join as A}from"node:path";var V="0.0.1";import{readdir as Je,access as Ye,constants as ze}from"node:fs/promises";import{join as c,basename as U}from"node:path";import{homedir as E}from"node:os";function W(){return process.env.XDG_CACHE_HOME||c(E(),".cache")}function q(){if(process.argv.includes("--no-auto-update")||process.argv.includes("--prefer-version"))return!1;let e=process.env.COPILOT_AUTO_UPDATE;return!(e&&e.toLowerCase()==="false")}function G(){let e=process.argv.indexOf("--prefer-version");if(!(e===-1||e+1>=process.argv.length))return process.argv[e+1]}function Xe(){if(process.platform==="darwin")return c(E(),"Library","Caches","copilot");if(process.platform==="win32"){let e=process.env.LOCALAPPDATA||c(E(),".cache");return c(e,"copilot")}return c(W(),"copilot")}function K(){let e=[];return process.env.COPILOT_CACHE_HOME&&e.push(c(process.env.COPILOT_CACHE_HOME,"pkg")),e.push(c(Xe(),"pkg")),e.push(c(W(),"copilot","pkg")),process.env.COPILOT_HOME&&e.push(c(process.env.COPILOT_HOME,"pkg")),e.push(c(E(),".copilot","pkg")),[...new Set(e)]}function j(e){let t=e.match(/^(\d+)\.(\d+)\.(\d+)/);if(t)return[Number(t[1]),Number(t[2]),Number(t[3])]}function Qe(e,t){let r=j(e),n=j(t);if(!r&&!n)return 0;if(!r)return-1;if(!n)return 1;for(let s=0;s<3;s++)if(r[s]!==n[s])return r[s]-n[s];let o=e.includes("-"),i=t.includes("-");return o!==i?o?-1:1:e.localeCompare(t)}async function J(e,...t){let r=[];for(let n of t){let o;try{o=await Je(n)}catch{continue}for(let i of o){let s=c(n,i);try{await Ye(c(s,e),ze.R_OK),r.push(s)}catch{continue}}}return r.sort((n,o)=>{let i=Qe(U(o),U(n));return i!==0?i:n.localeCompare(o)}),r}import{join as Ce}from"node:path";var P=Ke(xe(),1);function T(e={}){return(e.platform??process.platform)!=="linux"?"gnu":(e.detectLibcFamily??P.familySync)()===P.MUSL?"musl":"gnu"}function N(e=process.platform,t){let r=t??(e==="linux"?T():"gnu");return e==="linux"&&r==="musl"?"linuxmusl":e}function Ee(e=process.platform,t,r=process.arch){return`${N(e,t)}-${r}`}function Le(){let e=Ee();return K().flatMap(t=>[Ce(t,"universal"),Ce(t,e)])}function yt(){return process.env.COPILOT_CLI_VERSION?process.env.COPILOT_CLI_VERSION:"1.0.71-2"}async function Se(e,t){let r=A(e,"app.js"),n=yt()===V,o=G();if(t&&(o||q()&&!n)){let i=Le(),s=await J("app.js",...i);if(o){let a=s.find(h=>ht(h)===o);a?r=A(a,"app.js"):process.stderr.write(`Warning: preferred version ${o} not found in package cache, using built-in version | ||
| `)}else s.length>0&&(r=A(s[0],"app.js"))}return r}import{existsSync as bt}from"node:fs";import{basename as vt,resolve as xt}from"node:path";var Pe="extension_bootstrap.mjs";function Te(e,t,r=bt){let n=e.find(s=>vt(s)===Pe);if(!n)return;process.stderr.write(`[extension-fork] resolveBootstrapPath: __dir=${t}, argv-bootstrap=${n} | ||
@@ -12,0 +12,0 @@ `);let o=xt(t,"preloads",Pe),i=r(o);if(process.stderr.write(`[extension-fork] resolveBootstrapPath: localBootstrap=${o}, localExists=${i} |
+1
-1
| { | ||
| "name": "@github/copilot-linux-arm64", | ||
| "version": "1.0.71-1", | ||
| "version": "1.0.71-2", | ||
| "description": "GitHub Copilot CLI for linux-arm64", | ||
@@ -5,0 +5,0 @@ "license": "SEE LICENSE IN LICENSE.md", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 5 instances
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 4 instances
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
308652081
3.9%227
20.74%166848
1.79%268
30.1%38
8.57%