@dormer/electron-compat 0.0.0-stage → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +25 -4
- package/src/index.ts +61 -0
- package/src/matrix.ts +49 -0
- package/src/renderer.ts +18 -0
- package/test/compat.test.ts +26 -0
- package/README.md +0 -3
package/package.json
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dormer/electron-compat",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Electron-shaped adapter over @dormer/api: same module names where semantics hold, explicit NotSupportedError elsewhere",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0 OR MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts",
|
|
9
|
+
"./renderer": "./src/renderer.ts",
|
|
10
|
+
"./matrix": "./src/matrix.ts"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "node --test test/*.test.ts"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@dormer/api": "0.1.3",
|
|
17
|
+
"@dormer/renderer": "0.1.3"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/veerareddyvishal144/dormer.git",
|
|
25
|
+
"directory": "packages/electron-compat"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// `import { app, BrowserWindow, ipcMain } from "@dormer/electron-compat"` works for the supported
|
|
2
|
+
// surface; unsupported modules are exported as throwing stubs so failures are explicit and early.
|
|
3
|
+
import * as api from "@dormer/api";
|
|
4
|
+
import { MODULES } from "./matrix.ts";
|
|
5
|
+
|
|
6
|
+
export class NotSupportedError extends Error {
|
|
7
|
+
readonly code = "ENOTSUP";
|
|
8
|
+
readonly module: string;
|
|
9
|
+
constructor(module: string, detail: string) {
|
|
10
|
+
super(`Electron module '${module}' is not supported by Dormer: ${detail}`);
|
|
11
|
+
this.name = "NotSupportedError"; this.module = module;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function unsupported(name: string): unknown {
|
|
16
|
+
const entry = MODULES[name];
|
|
17
|
+
const detail = entry ? entry.notes : "no equivalent";
|
|
18
|
+
const thrower = () => { throw new NotSupportedError(name, detail); };
|
|
19
|
+
// A callable, constructible, property-less stub: any use throws with the reason.
|
|
20
|
+
return new Proxy(function stub() {}, {
|
|
21
|
+
apply: thrower, construct: thrower,
|
|
22
|
+
get(_t, prop) { if (prop === Symbol.toPrimitive || prop === "toString") return () => `[unsupported ${name}]`; return thrower(); },
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const anyApi = api as unknown as Record<string, unknown>;
|
|
27
|
+
const pick = (name: string, dormerName = name): unknown => (dormerName in anyApi ? anyApi[dormerName] : unsupported(name));
|
|
28
|
+
|
|
29
|
+
export const app = api.app;
|
|
30
|
+
export const BrowserWindow = api.BrowserWindow;
|
|
31
|
+
export const ipcMain = api.ipcMain;
|
|
32
|
+
export const Menu = pick("Menu");
|
|
33
|
+
export const MenuItem = pick("MenuItem");
|
|
34
|
+
export const Tray = pick("Tray");
|
|
35
|
+
export const dialog = pick("dialog");
|
|
36
|
+
export const Notification = pick("Notification");
|
|
37
|
+
export const clipboard = pick("clipboard");
|
|
38
|
+
export const globalShortcut = pick("globalShortcut");
|
|
39
|
+
export const shell = pick("shell");
|
|
40
|
+
export const nativeTheme = pick("nativeTheme");
|
|
41
|
+
export const screen = pick("screen");
|
|
42
|
+
export const powerMonitor = pick("powerMonitor");
|
|
43
|
+
export const utilityProcess = pick("utilityProcess", "BackgroundWorker");
|
|
44
|
+
export const safeStorage = pick("safeStorage");
|
|
45
|
+
export const session = unsupported("session");
|
|
46
|
+
export const protocol = unsupported("protocol");
|
|
47
|
+
export const desktopCapturer = unsupported("desktopCapturer");
|
|
48
|
+
export const BrowserView = unsupported("BrowserView");
|
|
49
|
+
export const autoUpdater = pick("autoUpdater", "updater");
|
|
50
|
+
export const crashReporter = unsupported("crashReporter");
|
|
51
|
+
export const net = unsupported("net");
|
|
52
|
+
export const netLog = unsupported("netLog");
|
|
53
|
+
export const MessageChannelMain = unsupported("MessageChannelMain");
|
|
54
|
+
export const TouchBar = unsupported("TouchBar");
|
|
55
|
+
export const systemPreferences = unsupported("systemPreferences");
|
|
56
|
+
export const inAppPurchase = unsupported("inAppPurchase");
|
|
57
|
+
|
|
58
|
+
// Electron code often checks process.versions.electron; give it something truthful instead.
|
|
59
|
+
(process.versions as Record<string, string | undefined>).dormer ??= "0.1.0";
|
|
60
|
+
|
|
61
|
+
export { MODULES, UNSUPPORTED, renderMatrixMarkdown } from "./matrix.ts";
|
package/src/matrix.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Compatibility matrix for Electron's main-process modules (ADR-0009). Status values:
|
|
2
|
+
// "full" same names and semantics
|
|
3
|
+
// "partial" present; some options/events differ or are missing (see notes)
|
|
4
|
+
// "renamed" available under a different Dormer name
|
|
5
|
+
// "none" cannot be reproduced on a system WebView; the adapter throws NotSupportedError
|
|
6
|
+
export type Status = "full" | "partial" | "renamed" | "none";
|
|
7
|
+
export interface Entry { status: Status; notes: string; dormer?: string }
|
|
8
|
+
|
|
9
|
+
export const MODULES: Record<string, Entry> = {
|
|
10
|
+
app: { status: "partial", notes: "Lifecycle events (will-finish-launching, ready, window-all-closed, before-quit, will-quit, quit, activate, open-url), whenReady, quit, exit, getPath, getName, getVersion. macOS: badge, recent documents, login items (SMAppService). Missing: jump lists (Windows), GPU info, accessibility APIs; see docs/api/platform-matrix.md." },
|
|
11
|
+
BrowserWindow: { status: "partial", notes: "Options and events for size, position, title, show/hide, close/closed (cancellable), focus/blur, resized, moved, fullscreen, alwaysOnTop, drag-drop of files. loadApp() replaces loadFile(); loadURL() is restricted by the capability origin policy. macOS: vibrancy, native tabs. Missing: BrowserView, setThumbarButtons, hookWindowMessage." },
|
|
12
|
+
webContents: { status: "partial", notes: "send, executeJavaScript (main world only), openDevTools, reload, setZoomFactor, print. macOS: capturePage, printToPDF (options ignored), findInPage (found yes/no), insertCSS/removeInsertedCSS, zoom levels. Missing: session, debugger, isolated-world execution, audio mute (no WKWebView API)." },
|
|
13
|
+
ipcMain: { status: "full", notes: "handle/handleOnce/removeHandler/on. Arguments are MessagePack-serialisable values; DOM objects, functions and MessagePorts do not cross. Adds handleStream for byte streams." },
|
|
14
|
+
ipcRenderer: { status: "partial", notes: "invoke/send/on/once/removeListener via the injected bridge. No sendSync, no postMessage with MessagePort transfer.", dormer: "window.dormer / @dormer/renderer" },
|
|
15
|
+
contextBridge: { status: "renamed", notes: "Dormer injects a frozen window.dormer object itself; there is no preload script and no exposeInMainWorld. Expose app-specific helpers from your renderer bundle instead.", dormer: "window.dormer" },
|
|
16
|
+
Menu: { status: "partial", notes: "buildFromTemplate, setApplicationMenu (app menu on macOS, per-window elsewhere), popup, roles, accelerators, check/radio items. On macOS the host installs a default App/Edit/Window menu (Cmd+Q, Cmd+C/V) until setApplicationMenu is called. Missing: sharingItem, badge, sublabel/toolTip on some platforms." },
|
|
17
|
+
Tray: { status: "partial", notes: "Icon, context menu, tooltip, title (macOS), click events. Linux AppIndicator backend emits no click events and GNOME needs an extension." },
|
|
18
|
+
dialog: { status: "partial", notes: "showOpenDialog, showSaveDialog, showMessageBox. Missing: securityScopedBookmarks, showCertificateTrustDialog, showErrorBox styling." },
|
|
19
|
+
Notification: { status: "partial", notes: "title, body, silent, icon, click/close where the OS reports them. Missing: actions/reply on most platforms, toastXml, Windows activation callbacks (planned)." },
|
|
20
|
+
clipboard: { status: "partial", notes: "readText/writeText/readHTML/writeHTML/readImage/writeImage/clear; macOS bookmark and find pasteboards. Missing: selection clipboard write on Linux." },
|
|
21
|
+
globalShortcut: { status: "partial", notes: "register/unregister/isRegistered/unregisterAll. Not available on Wayland (ENOTSUP)." },
|
|
22
|
+
shell: { status: "partial", notes: "openExternal (scheme allow-list), openPath, showItemInFolder, trashItem, beep (macOS/Linux). Missing: readShortcutLink/writeShortcutLink." },
|
|
23
|
+
nativeTheme: { status: "partial", notes: "shouldUseDarkColors, updated event. themeSource applies to the page on macOS (WKWebView appearance); host-drawn UI only elsewhere." },
|
|
24
|
+
screen: { status: "partial", notes: "getPrimaryDisplay, getAllDisplays (bounds, scaleFactor); macOS: getCursorScreenPoint, display-metrics-changed. Missing elsewhere: cursor point, display-change events." },
|
|
25
|
+
powerMonitor: { status: "partial", notes: "macOS: suspend/resume/lock-screen/unlock-screen/screens-sleep/wake events, getSystemIdleTime/State. Windows/Linux planned. isOnBatteryPower not implemented." },
|
|
26
|
+
session: { status: "none", notes: "Cookies, webRequest interception, proxies, permissions handler, cache control live in the WebView engine; only subsets exist and differ per platform." },
|
|
27
|
+
protocol: { status: "renamed", notes: "dormer://app serves bundled assets with CSP; arbitrary privileged schemes (standard/secure/bypassCSP/corsEnabled flags) are not configurable.", dormer: "dormer://app (built in)" },
|
|
28
|
+
desktopCapturer: { status: "none", notes: "Requires Chromium's DesktopMediaList; use OS screen capture APIs from Node instead." },
|
|
29
|
+
webview: { status: "none", notes: "<webview> tag is Chromium-only. Use a second BrowserWindow." },
|
|
30
|
+
BrowserView: { status: "none", notes: "Multiple web contents composited in one window are possible via child webviews in WRY but not exposed yet." },
|
|
31
|
+
utilityProcess: { status: "renamed", notes: "Use BackgroundWorker (worker_threads with the Dormer frame protocol) or child_process from Node.", dormer: "BackgroundWorker" },
|
|
32
|
+
autoUpdater: { status: "renamed", notes: "Dormer ships a cross-platform updater with Ed25519-signed manifests (host.updater.*).", dormer: "updater" },
|
|
33
|
+
safeStorage: { status: "partial", notes: "macOS: AES-256-GCM with the key in the login Keychain. DPAPI and Secret Service planned." },
|
|
34
|
+
crashReporter: { status: "none", notes: "Not provided; use process.report / a Node crash reporter." },
|
|
35
|
+
net: { status: "renamed", notes: "Use Node's fetch/undici in the backend.", dormer: "node:http / fetch" },
|
|
36
|
+
netLog: { status: "none", notes: "Chromium network log is not available." },
|
|
37
|
+
inAppPurchase: { status: "none", notes: "Not provided." },
|
|
38
|
+
systemPreferences: { status: "none", notes: "Not provided." },
|
|
39
|
+
TouchBar: { status: "none", notes: "Not provided." },
|
|
40
|
+
MessageChannelMain: { status: "none", notes: "MessagePorts do not cross the WebView boundary on system WebViews." },
|
|
41
|
+
process: { status: "partial", notes: "Plain Node process object in the backend; process.versions.electron is absent, process.versions.dormer is set by the adapter." },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const UNSUPPORTED = Object.entries(MODULES).filter(([, e]) => e.status === "none").map(([k]) => k);
|
|
45
|
+
|
|
46
|
+
export function renderMatrixMarkdown(): string {
|
|
47
|
+
const rows = Object.entries(MODULES).map(([name, e]) => `| \`${name}\` | ${e.status} | ${e.dormer ? `\`${e.dormer}\`` : ""} | ${e.notes} |`);
|
|
48
|
+
return ["| Electron module | Status | Dormer equivalent | Notes |", "|---|---|---|---|", ...rows].join("\n");
|
|
49
|
+
}
|
package/src/renderer.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// `ipcRenderer` shim for renderer code written against Electron, backed by the injected bridge.
|
|
2
|
+
import { invoke as dInvoke, send as dSend, on as dOn, once as dOnce, isDormer } from "@dormer/renderer";
|
|
3
|
+
|
|
4
|
+
type Listener = (event: { senderId: number }, ...args: any[]) => void;
|
|
5
|
+
const offs = new Map<Listener, () => void>();
|
|
6
|
+
const EV = { senderId: 0 };
|
|
7
|
+
|
|
8
|
+
export const ipcRenderer = {
|
|
9
|
+
invoke<T = unknown>(channel: string, ...args: unknown[]): Promise<T> { return dInvoke<T>(channel, ...args); },
|
|
10
|
+
send(channel: string, ...args: unknown[]): void { dSend(channel, ...args); },
|
|
11
|
+
on(channel: string, listener: Listener): typeof ipcRenderer { offs.set(listener, dOn(channel, (...args) => listener(EV, ...args))); return ipcRenderer; },
|
|
12
|
+
once(channel: string, listener: Listener): typeof ipcRenderer { offs.set(listener, dOnce(channel, (...args) => listener(EV, ...args))); return ipcRenderer; },
|
|
13
|
+
removeListener(_channel: string, listener: Listener): typeof ipcRenderer { offs.get(listener)?.(); offs.delete(listener); return ipcRenderer; },
|
|
14
|
+
removeAllListeners(_channel?: string): typeof ipcRenderer { for (const off of offs.values()) off(); offs.clear(); return ipcRenderer; },
|
|
15
|
+
sendSync(): never { throw new Error("ipcRenderer.sendSync is not supported by Dormer (no synchronous IPC on system WebViews); use invoke"); },
|
|
16
|
+
postMessage(): never { throw new Error("ipcRenderer.postMessage with MessagePort transfer is not supported by Dormer"); },
|
|
17
|
+
};
|
|
18
|
+
export const isElectronLike = isDormer;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
test("matrix is consistent and renders", async () => {
|
|
5
|
+
const { MODULES, UNSUPPORTED, renderMatrixMarkdown } = await import("../src/matrix.ts");
|
|
6
|
+
assert.ok(Object.keys(MODULES).length > 25);
|
|
7
|
+
assert.ok(UNSUPPORTED.includes("session") && UNSUPPORTED.includes("desktopCapturer"));
|
|
8
|
+
const md = renderMatrixMarkdown();
|
|
9
|
+
assert.match(md, /\| `ipcMain` \| full \|/);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("unsupported stubs throw NotSupportedError with the reason", async () => {
|
|
13
|
+
// @dormer/api connects to the host when imported; give it a dummy channel first (as app.test.ts does).
|
|
14
|
+
const net = await import("node:net");
|
|
15
|
+
const { HostChannel } = await import("@dormer/node-runtime");
|
|
16
|
+
const { initRuntime } = await import("../../api/src/runtime.ts");
|
|
17
|
+
const server = net.createServer(); await new Promise<void>((r) => server.listen(0, "127.0.0.1", r));
|
|
18
|
+
const port = (server.address() as import("node:net").AddressInfo).port;
|
|
19
|
+
const sock = await new Promise<import("node:net").Socket>((r) => { const s = net.connect(port, "127.0.0.1", () => r(s)); });
|
|
20
|
+
initRuntime(new HostChannel(sock));
|
|
21
|
+
const mod = await import("../src/index.ts").catch((e) => { throw new Error("adapter import failed: " + e.message); });
|
|
22
|
+
assert.throws(() => (mod.session as any).defaultSession, (e: Error) => e.name === "NotSupportedError" && /session/.test(e.message));
|
|
23
|
+
assert.throws(() => new (mod.BrowserView as any)(), /BrowserView/);
|
|
24
|
+
assert.equal(typeof mod.ipcMain.handle, "function");
|
|
25
|
+
sock.destroy(); server.close();
|
|
26
|
+
});
|
package/README.md
DELETED