@bobfrankston/msgapidefs
v0.1.40
Published
TypeScript definitions for msgapi JavaScript API (msgview/msger)
Maintainers
Readme
@bobfrankston/msgapidefs
⚠️ SECURITY: msgapi gives full native OS access (filesystem, processes, networking). Intended ONLY for trusted apps — native apps written as web pages. Do not expose to untrusted content.
⚠️ EXPERIMENTAL: APIs are experimental and subject to change.
TypeScript definitions for the msgapi JavaScript API used by msga, msger, and msgview. See the Typescript definitions in msgapidefs.ts for more info
Workspace Structure
msgx (workspace root)
├── msgapidefs - TypeScript type definitions for window.msgapi (this package)
├── msgcommon - Shared CLI parsing, templates, pinning utilities
├── msger - Rust/wry webview host (Windows, Linux, Mac, Pi) — PRIMARY
├── msgview - Electron webview host (desktop) — SEMI-RETIRED, see below
├── msga - .NET MAUI webview host (Android, Windows)
├── build - Release build orchestration script (build/build.ts)
└── md - Shared documentationInstallation
npm install @bobfrankston/msgapidefsUsage
// Import to register global window.msgapi type
import '../node_modules/@bobfrankston/msgapidefs/msgapidefs.js';
// Note: '../node_modules/@bobfrankston/msgapidefs/index.js' may also work via package.json exports
// Or import types explicitly
import type { MsgAPI, MsgResult } from '../node_modules/@bobfrankston/msgapidefs/msgapidefs.js';
// msgapi provides APIs not available in browsers/webviews
if (window.msgapi) {
// Window control beyond browser capabilities
window.msgapi.setFullscreen(true);
window.msgapi.setAlwaysOnTop(true);
// File system access (requires allowFs flag)
const file = await window.msgapi.fs?.selectFile();
}
// For standard browser APIs, use native methods:
document.title = 'My App'; // Instead of msgapi.setTitle()
localStorage.setItem('key', 'value'); // Instead of msgapi.saveData()
window.close(); // Instead of msgapi.close() if not passing resultPlatform Support
✅ = Current | 🔧 = Possible (recompile/rebuild needed) | ❌ = Not feasible
| Platform | msger (Rust/wry) | msgview (Electron) | msga (.NET MAUI) | |----------|:----------------:|:------------------:|:----------------:| | Windows | ✅ | ✅ | ✅ | | Linux | ✅ | ✅ | ❌ | | macOS | 🔧 wry supports it | ✅ | 🔧 Mac Catalyst | | Raspberry Pi | ✅ | ✅ | ❌ | | Android | ❌ | ❌ | ✅ (primary) | | iOS | ❌ | ❌ | ❌ |
msger uses wry (Rust webview) and is the primary host. Desktop platforms (Win/Linux/Mac) are mature. The Pi display bug ("runs but renders shredded scanlines") was WebKitGTK's DMABUF renderer on vc4/v3d — msger now sets
WEBKIT_DISABLE_DMABUF_RENDERER=1automatically on Raspberry Pi hardware (both the JS launcher and the binary), and the Pi renders correctly as of 2026-07-31. msgview is semi-retired as of 2026-07-31 — see below. msga targets Android primarily; Windows for dev/testing. iOS would be the most realistic path — MAUI supports iOS natively, so msga could target it with an iOS build profile, Apple developer account, and platform-specific conditional code (#if IOS) for permissions and APIs. No current plans.
msgview is semi-retired (2026-07-31)
msger + msga cover every platform, so the Electron host has no platform to itself. Do not do feature-parity work on msgview. New features land in msger (and msga where it makes sense); msgview catches up only on demand, if a real need appears.
As of 2026-08-01 msgview has no capability msger lacks. The two things that used to be Electron-only are both settled:
- Unpacked Chrome extensions (
-extension) — not how add-ins are done here.window.msgapi(this package) plus injected init scripts is the add-in mechanism:initScript/initScriptPathinject app JS into every document before page scripts run, with the full msgapi surface available — host-native, works in msger and msga, no Chromium required. If something genuinely needs Chrome extension APIs, run it in Chrome. -renderoff Windows — ported. msger now captures on Linux/Pi through WebKitGTK's snapshot API (see the render row above), verified on a Pi 5 for both PNG and JPEG output.
Engine currency is not a reason: a feature probe on Pi 5 / WebKitGTK 2.50.6
(2026-07-31) passed 23 of 24 modern checks — :has(), CSS nesting, container
queries, subgrid, backdrop-filter, color-mix, oklch, Popover, View
Transitions, Object.groupBy, Promise.withResolvers, Intl.Segmenter, WebGL2,
OffscreenCanvas — failing only the RegExp v flag. The WebKitGTK user-agent token
(Version/60.5) is a WebKitGTK version string, not a Safari generation; it says
nothing about capability.
Implementation Status
✅ = Implemented | ⚠️ = Partial | ❌ = Not implemented
| API | msgview | msger | msga | Notes |
|-----|:-------:|:-----:|:----:|-------|
| Configuration | | | | |
| version | ❌ | ❌ | ✅ | window.msgapi.version string |
| setLogging(value?) | ❌ | ❌ | ✅ | Returns previous state |
| setAutoUpdate(value?) | ❌ | ❌ | ✅ | 'auto'/'check'/'off'; msga Android only |
| Diagnostics | | | | window.msgapi.diag.* |
| diag.getExitReasons(max?) | ❌ | ❌ | ✅ | Why the previous run exited; msga Android 11+ only, [] elsewhere |
| Window Control | | | | window.msgapi.* |
| toggleFullscreen() | ❌ | ✅ | ✅ | msger: via window.msgapi (msger-api.js) |
| setFullscreen(bool) | ✅ | ✅ | ✅ | |
| minimize() | ✅ | ✅ | ✅ | msga: Windows only |
| maximize() | ✅ | ✅ | ✅ | msga: Windows only |
| setSize(w, h) | ✅ | ✅ | ✅ | msga: Windows only |
| setPosition(x, y) | ✅ | ✅ | ✅ | msga: Windows only |
| setAlwaysOnTop(bool) | ✅ | ✅ | ❌ | |
| close(result?) | ✅ | ✅ | ✅ | |
| navigate(url) | ❌ | ❌ | ✅ | msga only |
| reload() | ❌ | ❌ | ✅ | msga only |
| File System | | | | window.msgapi.fs.* |
| fs.selectFile() | ❌ | ❌ | ✅ | msga: full JS bridge wired |
| fs.selectFiles() | ❌ | ❌ | ✅ | msga: full JS bridge wired |
| fs.saveFileAs() | ❌ | ❌ | ✅ | msga: Windows only |
| fs.selectFolder() | ❌ | ❌ | ✅ | msga: Windows only |
| fs.read(path) | ❌ | ❌ | ✅ | msga: binary→base64→TextDecoder |
| fs.readAsDataUrl(path) | ❌ | ❌ | ✅ | msga: auto-detects MIME from extension |
| fs.write(path, content) | ❌ | ❌ | ✅ | |
| fs.list(path) | ❌ | ❌ | ✅ | msga: includes attributes/created on Windows |
| fs.exists(path) | ❌ | ❌ | ✅ | |
| fs.delete(path) | ❌ | ❌ | ✅ | |
| Shell | | | | window.msgapi.shell.* |
| shell.exec() | ❌ | ❌ | ✅ | Process with stdout/stderr; limited on non-rooted Android |
| shell.open() | ❌ | ❌ | ✅ | Android: Intent.ACTION_VIEW; Windows: ShellExecute |
| shell.showInFolder() | ❌ | ❌ | ❌ | Defined in interface, not yet implemented |
| shell.trash() | ❌ | ❌ | ❌ | Defined in interface, not yet implemented |
| UDP Networking | | | | window.msgapi.udp.* |
| udp.send() | ✅ | ✅ | ✅ | Node dgram / Rust UdpSocket / C# UdpClient |
| udp.listen() | ✅ | ✅ | ✅ | Background receive loop in all hosts |
| udp.sendReceive() | ✅ | ✅ | ✅ | Send + wait with timeout |
| udp.broadcast() | ✅ | ✅ | ✅ | 255.255.255.255 broadcast |
| TCP Networking | | | | window.msgapi.tcp.* |
| tcp.connect() | ✅ | ✅ | ✅ | Node net/tls / Rust TcpStream+native-tls / C# TcpClient+SslStream |
| tcp.write() | ✅ | ✅ | ✅ | UTF-8 string data |
| tcp.onData() | ✅ | ✅ | ✅ | Persistent callback, fires multiple times |
| tcp.onClose() | ✅ | ✅ | ✅ | Connection closed notification |
| tcp.onError() | ✅ | ✅ | ✅ | Error notification |
| tcp.upgradeTLS() | ✅ | ✅ | ✅ | STARTTLS — upgrade plaintext to TLS |
| tcp.close() | ✅ | ✅ | ✅ | Close connection |
| HTTP Fetch | | | | window.msgapi.http.* |
| http.fetch() | ✅ | ✅ | ✅ | Native HTTP — bypasses CORS/mixed-content |
| Media capture (mic/camera) | ❌ | ❌ | ❌ | Not an msgapi API — getUserMedia is the engine's. What's missing is the permission grant, which no host makes: wry's WebKitGTK backend has no permission-request handling (WebKitGTK denies by default), msger ignores WebView2's PermissionRequested, msga lacks RECORD_AUDIO. Deferred; direction is an external speech package, not in-page capture — see msgapi-plan.md. |
| Host Launcher Features | | | | Not part of window.msgapi; CLI / launcher-side |
| Markdown rendering (.md/.markdown auto-render) | ✅ | ✅ | ❌ | Local file:// URL → rendered HTML via msgcommon/markdown (marked + light/dark CSS). msga could match it in MainPage.xaml.cs URL interception — not done yet. |
| Unpacked Chrome extension loading (-extension <dir>) | ✅ | ❌ | ❌ | msgview-only via session.extensions.loadExtension. wry has no public extension API; WebKitGTK has none at all. Not the add-in mechanism here — app add-ins are window.msgapi + injected init scripts (initScript / initScriptPath), which work in msger and msga without Chromium. Chrome-extension-specific needs go to Chrome. |
| Headless mode (-noshow without -save) | ✅ | ✅ | ❌ | msger creates the WebView invisible (with_visible(false)) but still loads HTML and runs scripts — an embedded <script> can query the engine and window.ipc.postMessage a result back. Combined with -result <fieldname> for clean stdout, this lets msger act as a headless CSS/JS resolver (contrast-color(), getComputedStyle(), CSS.supports()). msgview matches with BrowserWindow({show:false}) + backgroundThrottling:false. |
| Render to bitmap (-render [file], render option) | ✅ | ✅ | ❌ | Headless page→image capture: the window is never shown; the page loads, settles (renderDelay, default 100ms after load + two rAFs), is screenshotted, and the host exits. -render <file> writes the image (format from extension: .png/.jpg/.bmp) and the JSON result carries render: {path, width, height, format}; bare -render (or API render: true) returns it as render: {data (base64), width, height, format}. Library API: showMessageBox({render: "shot.png" \| true, renderDelay?, renderFormat?}); a failed capture rejects (msger) or sets renderError in the result. Both hosts fall back to capturing whatever rendered if the page hasn't loaded within the timeout (default 30s in render mode). msger: WebView2 DevTools Page.captureScreenshot on Windows, webkit_web_view_get_snapshot() → cairo PNG on Linux/Pi (2026-08-01); msgview: webContents.capturePage(). Both msger paths produce the same base64 PNG, so format transcoding and file writing are shared. Linux caveat: WebKitGTK cannot snapshot a window that was never mapped, so on Linux the render window IS mapped — made invisible by going fully transparent and skipping the taskbar, since Wayland has no way to park a window off-screen. Width/height are physical pixels (include DPI scale) and cover the WebView viewport, not the window frame. |
| Desktop overlay (-overlay, overlay option) | ❌ | ✅ | ❌ | msger-only (2026-08-09). Frameless + transparent window drawn over the desktop, click-through, always-on-top, kept out of the taskbar and never focused — for transient notices such as per-monitor identification. Needs all four of with_decorations(false), with_transparent(true) on the tao window, with_transparent(true) on the wry WebView, and a page that declares its own transparent background (the built-in template does; -url/-raw pages must). Click-through is set_ignore_cursor_events (Windows/Mac/X11/Wayland; best-effort, warns and stays clickable if refused). No taskbar slot on Windows/Linux only — macOS has no per-window equivalent, Dock presence being a process-level activation policy. Pair with timeout: an overlay has no close button and swallows no clicks, so nothing in it can be operated. Each overlay defaults to its own WebView2 profile keyed by screen, because concurrent WebView2 creations against one user-data dir fail with 0x80070057 — which is what launching one overlay per display does. Verified on Windows; Linux/Pi and Mac are built but not yet exercised. Linux/X11 additionally needs a running compositor or transparency renders black. |
| Monitor enumeration (-list, listMonitors option) | ❌ | ✅ | ❌ | msger-only (2026-08-09). Prints {"monitors":[{index,name,x,y,width,height,scale,primary}]} and exits without creating a window. Indices match -screen, ordered bottom-row-first then left-to-right (the ordering -screen has always used on Windows, preserved so existing indices keep pointing at the same display). Exists so a caller can discover how many displays there are, and what to label them, before spawning one overlay per display. |
| Screen selection (-screen <n>, screen option) | ❌ | ✅ | ❌ | Offsets -pos into the named monitor and picks which display -fullscreen covers. Was Windows-only for -pos; the gate was wrong rather than protective, so Mac/X11 now honor it too (2026-08-09). fullscreen + screen is the only portable way to target a display: Wayland does not let a client position its own window at absolute coordinates, but it does honor fullscreen on a chosen output. |
msger now injects
window.msgapiviamsger-api.jswith window control, UDP, and HTTP. File system and shell are not yet implemented in msger or msgview.Markdown rendering (last two rows): both msger and msgview detect
.md/.markdownURLs pointing at local files and render them via the sharedrenderMarkdown()helper inmsgcommon/markdown— no extensions, no per-machine setup, GFM with light/dark CSS that followsprefers-color-scheme. Library callers canimport { renderMarkdown, looksLikeMarkdown } from '@bobfrankston/msgcommon/markdown'directly. Extension loading is msgview-only by design: see the row's note.
httpudp-client auto-detects window.msgapi?.udp and uses native UDP when available, falling back to the httpudp WebSocket proxy otherwise.
See msgapi-plan.md for the full implementation plan and roadmap.
API Reference
Configuration
version- Host app version string (e.g.,"1.2.0")setLogging(value?)- Control debug logging. Returns previous state as string.'msga'- C# host logging only (logit-msga)'msgapi'- JS bridge logging only (logit-msgapi)'on'- Both streams'off'/'none'- All offnull/''- Query current state without changing- Returns:
'msga','msgapi','msga,msgapi', or'none'
setAutoUpdate(value?)- Control APK auto-update behavior. Returns previous state as string. msga Android only.'auto'- Check versions.json, download + install silently (no prompt)'check'- Check versions.json, prompt "Install now?" before downloading'off'- No update checkingnull/undefined- Query current state without changing- Default:
'auto'(configurable viaAUTO_UPDATEin msga.env) - Checks on startup then every
UPDATE_INTERVAL_MINUTES(default 60)
Diagnostics
Why did the previous run exit? Android records the last few process deaths (ApplicationExitInfo, Android 11+); the host reads its own history on the next run and exposes it to the web app. This distinguishes crash / ANR / low-memory kill / cached-app freezer — no adb needed. Currently msga Android only; other hosts/platforms return [].
diag.getExitReasons(max?)- Previous-run exit records, newest first (default/max clamp: 5/16). ReturnsMsgExitInfo[]:timestamp- Exit time, ms since epochreason-"LowMemory","Crash","CrashNative","Anr","Freezer","ExitSelf","Signaled","UserRequested","UserStopped","DependencyDied","ExcessiveResourceUsage","PackageUpdated","Other", ... (AndroidApplicationExitInforeason names)description- OS-provided detail (may be null), e.g."isolated not needed"= renderer cleanupimportance- Process state at death (e.g."Foreground","Cached") — aCached+LowMemorykill is normal Android memory reclaim, not a bugpss/rss- Memory at exit, KBprocessName- Which process died: the main package name, or...:sandboxed_process*= the WebView renderer. Records cover every process in the package — renderer deaths are routine cleanup, not the app dying; filter by processName to find real main-process exitspid- Process ID at exitstatus- Signal number forSignaled, exit code forExitSelf, 0 otherwisestateSummary- App-set marker (ActivityManager.setProcessStateSummary); msga stampsstart=<ISO time> v=<version>at each launch so uptime-at-death is computabletrace- First ~4KB of the ANR/native-crash trace when the OS captured one (Anr/CrashNativeonly)
// Exit-history tool in a web app (e.g. houser debug panel)
const exits = await window.msgapi?.diag?.getExitReasons() ?? [];
for (const e of exits) {
console.log(`${new Date(e.timestamp).toLocaleString()} ${e.reason}` +
` (${e.importance ?? '?'}, pss=${e.pss ?? '?'}KB) ${e.description ?? ''}`);
}Interpreting common reasons: LowMemory = killed by the low-memory killer; Anr = main thread hung; Crash/CrashNative = unhandled exception / native crash; Freezer = app was frozen (Android 12+ cached-app freezing — UDP listeners silently suspended, app not actually dead); UserRequested = swiped away from recents; PackageUpdated = replaced by an APK update; Signaled = killed by signal (check description).
msga also logs new exit records via MsgALog at startup when LOGGING=true in msga.env (Previous exit: ...), deduplicated across runs via a lastSeenExitTimestamp preference. See msga/exit-diagnostics.md for the full design notes.
Window Control
toggleFullscreen()- Toggle fullscreen modesetFullscreen(enabled: boolean)- Set fullscreen stateminimize()- Minimize windowmaximize()- Toggle maximize/restoresetSize(width, height)- Set window sizesetPosition(x, y)- Set window positionsetAlwaysOnTop(enabled: boolean)- Keep window on topclose(result?)- Close window and return result to parent process
File System
File dialogs (user picks interactively — safe without special permissions):
fs.selectFile(options?)- Open file pickerfs.selectFiles(options?)- Open multi-file pickerfs.saveFileAs(content, filename?, options?)- Save file dialogfs.selectFolder(options?)- Folder picker
Direct path operations (give the webview full filesystem access — msga only currently):
fs.read(path, options?)- Read file. Options:{ encoding: 'utf8' | 'base64' | 'binary' }. Default'utf8'returns string;'base64'returns raw base64 string (efficient for images);'binary'returnsUint8Array.fs.readAsDataUrl(path, mimeType?)- Read file as data URL (data:image/jpeg;base64,...). Auto-detects MIME from extension if omitted.fs.write(path, content)- Write filefs.list(path)- List directory. ReturnsFileInfo[]withattributes(Win32 flags) andcreatedon Windows.fs.exists(path)- Check if file/directory existsfs.delete(path)- Delete file or directory
Note: File system operations are currently implemented only in msga. msger and msgview do not yet have
window.msgapi.fs— these hosts have their own internal fs mechanisms but have not wired them to the msgapi interface.
UDP Networking
Native UDP socket access — no WebSocket proxy needed when running inside a msgapi host.
udp.send(host, port, data)- Send UDP packet to specific hostudp.listen(port, callback)- Listen for incoming UDP packets (returns{close()}handle)udp.sendReceive(host, port, data, timeoutMs?)- Send and wait for responseudp.broadcast(port, data)- Broadcast to local network
TCP Networking
Native TCP/TLS socket access — for IMAP, SMTP, and other stream protocols. Connections are long-lived; use stream IDs to manage multiple connections. TLS is handled natively — JS never sees the raw TLS handshake.
tcp.connect(host, port, tls)- Connect to host:port. Iftlsis true, connect with TLS directly (port 993). Returns astreamId(number).tcp.write(streamId, data)- Write UTF-8 string data to stream.tcp.onData(streamId, callback)- Register handler for incoming data. Persistent — fires multiple times as data arrives.tcp.onClose(streamId, callback)- Register handler for connection close.hadErroris true if closed due to error.tcp.onError(streamId, callback)- Register handler for errors.tcp.upgradeTLS(streamId, servername)- Upgrade a plaintext connection to TLS (STARTTLS). Used by IMAP/SMTP after negotiating capabilities on plaintext.tcp.close(streamId)- Close the connection.
// Connect to Gmail IMAP (direct TLS on port 993)
const id = await window.msgapi.tcp.connect('imap.gmail.com', 993, true);
window.msgapi.tcp.onData(id, (data) => console.log('Server:', data));
window.msgapi.tcp.onClose(id, (hadError) => console.log('Closed', hadError));
window.msgapi.tcp.onError(id, (msg) => console.error('Error:', msg));
// STARTTLS example (connect plaintext, then upgrade)
const id2 = await window.msgapi.tcp.connect('imap.example.com', 143, false);
// ... negotiate STARTTLS capability ...
await window.msgapi.tcp.upgradeTLS(id2, 'imap.example.com');
// ... now on encrypted connection ...Platform implementations: msgview uses Node.js net/tls, msger uses Rust std::net::TcpStream + native-tls, msga uses C# TcpClient + SslStream.
Used by: @bobfrankston/iflow-direct BridgeTransport — enables browser/WebView IMAP without a Node.js server.
Shell / Process Execution
Native process execution — run commands, open files with default handler. msga only currently.
shell.exec(command, args?, options?)- Run external command and capture output. Returns{ stdout, stderr, exitCode }. Options:cwd,timeout(ms),stdin,encoding('utf8'|'base64'). Limited on non-rooted Android.shell.open(path)- Open file/URL with system default handler (Windows: ShellExecute, Android: Intent.ACTION_VIEW)shell.showInFolder(path)- Select file in Explorer / file manager (not yet implemented)shell.trash(path)- Move to Recycle Bin / Trash (not yet implemented)
HTTP Fetch
Native HTTP client — bypasses browser CORS and mixed-content restrictions. Use when an HTTPS-served web app needs to reach HTTP endpoints (e.g., local devices on the LAN).
http.fetch(url, init?)- Drop-in replacement for browserfetch(). SameRequestInitparams, returns a realResponseobject (.ok,.status,.json(),.text(),.headers,.clone(), etc.)
// HTTP from an HTTPS page — blocked by browsers, works via msgapi
const r = await window.msgapi.http.fetch('http://192.168.1.5/api/status');
const data = await r.json();
// POST with headers and body
const r2 = await window.msgapi.http.fetch('http://192.168.1.5/api/cmd', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cmd: 'reboot' })
});Platform implementations: msga uses C# HttpClient, msger uses Rust ureq, msgview uses Node.js fetch(). All return a real Response object constructed on the JS side.
Unsupported init fields (silently ignored — not meaningful for native HTTP): mode, credentials, cache, signal, redirect. new Headers() objects are normalized to plain objects.
Examples
See samples.html for interactive examples and demos of all API features!
Test in msgview:
msgview -url "file:///path/to/node_modules/@bobfrankston/msgapidefs/samples.html"Test in msger:
msger -url "file:///path/to/node_modules/@bobfrankston/msgapidefs/samples.html"Test in browser:
# Open samples.html in your browser to test browser compatibilityThe samples demonstrate:
- Window control (fullscreen, minimize, maximize, resize, position)
- Data persistence (save/load user preferences, session data, recent files)
- Presentation mode example
- Session management
- And more!
Cross-Platform Implementation
msgapidefs defines the TypeScript interface only. Each host implements the window.msgapi bridge differently:
msgview (Electron)
- Bridge: Electron
contextBridge.exposeInMainWorld('msgapi', ...)inpreload/preload.ts - Mechanism:
ipcRenderer.invoke()calls to main process IPC handlers - Window control: Electron
BrowserWindowAPI - File system: Node.js
fsmodule via IPC, file dialogs via Electrondialog
msger (Rust/wry)
- Bridge: JavaScript injected via wry
with_initialization_script()inmsger-native/src/main.rs - JS API:
msger-native/src/msger-api.jscreateswindow.msgapiobject - Mechanism:
window.ipc.postMessage(JSON.stringify(...))to Rust IPC handler - Capabilities: Window control, UDP, HTTP fetch via
window.msgapi - Limitations: wry 0.47.2 has IPC issues;
close()useswindow.close()workaround
msga (.NET MAUI)
- Bridge: JavaScript injected via
WebViewControl.EvaluateJavaScriptAsync()inMainPage.xaml.cs - JS API:
GetMsgApiScript()createswindow.msgapiobject inline - Mechanism:
msgapi://URL interception — JS creates hidden iframes, C# intercepts inOnNavigating - Async: Promise callbacks via
callNativeAsync()→window._msgapiResolve()/_msgapiReject() - Window control: Platform-conditional (
#if WINDOWSuses WinUI AppWindow APIs) - File system: MAUI
FilePicker, Windows-specificFileSavePicker/FolderPicker - Platforms: Android (primary target), Windows (development/testing)
Adding New Sub-Objects
Each capability is an optional sub-object on MsgAPI (e.g., fs?, udp?). To add a new one:
- Define the interface in
msgapidefs.tsas an optional sub-object onMsgAPI - Implement the native side in each host:
- msgview: Add IPC handlers in
main.ts, expose inpreload/preload.ts - msger: Add Rust handler in
main.rs, expose inmsger-api.js - msga: Add C# methods + JS injection in
GetMsgApiScript()(see msga readme)
- msgview: Add IPC handlers in
- Browser code uses optional chaining:
window.msgapi?.newFeature?.method()— works everywhere, undefined when unavailable
See msgapi-plan.md for the full feature matrix and implementation status across all three hosts.
Related Packages
- @bobfrankston/msgview - Electron-based message box (slower, works on Pi)
- @bobfrankston/msger - Rust/wry-based message box (fast, lightweight)
- msga - .NET MAUI Android/Windows wrapper app
License
ISC
