🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@github/copilot-linux-arm64

Package Overview
Dependencies
Maintainers
24
Versions
296
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@github/copilot-linux-arm64 - npm Package Compare versions

Comparing version
1.0.71-1
to
1.0.71-2
assets/copilot.png

Sorry, the diff of this file is not supported yet

+34
{
"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`
![https://github.com/webviewjs/webview/actions](https://github.com/webviewjs/webview/workflows/CI/badge.svg)
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.
![preview](https://github.com/webviewjs/webview/raw/main/assets/preview.png)
> [!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`
![https://github.com/webviewjs/webview/actions](https://github.com/webviewjs/webview/workflows/CI/badge.svg)
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.
![preview](https://github.com/webviewjs/webview/raw/main/assets/preview.png)
> [!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}

{
"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