@bobfrankston/msgapidefs 0.1.29 → 0.1.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,24 @@
1
1
  # @bobfrankston/msgapidefs
2
2
 
3
- TypeScript definitions for the **msgapi** JavaScript API used in msgview and msger.
3
+ > **⚠️ 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.
4
+
5
+ > **⚠️ EXPERIMENTAL:** APIs are experimental and subject to change.
6
+
7
+ TypeScript definitions for the **msgapi** JavaScript API used by msga, msger, and msgview. See the Typescript definitions in
8
+ [msgapidefs.ts](msgapidefs.ts) for more info
9
+
10
+ ## Workspace Structure
11
+
12
+ ```
13
+ msgx (workspace root)
14
+ ├── msgapidefs - TypeScript type definitions for window.msgapi (this package)
15
+ ├── msgcommon - Shared CLI parsing, templates, pinning utilities
16
+ ├── msger - Rust/wry webview host (Windows, Linux)
17
+ ├── msgview - Electron webview host (Windows, Linux, Mac, Pi)
18
+ ├── msga - .NET MAUI webview host (Android, Windows)
19
+ ├── build - Release build orchestration script (build/build.ts)
20
+ └── md - Shared documentation
21
+ ```
4
22
 
5
23
  ## Installation
6
24
 
@@ -34,8 +52,94 @@ localStorage.setItem('key', 'value'); // Instead of msgapi.saveData()
34
52
  window.close(); // Instead of msgapi.close() if not passing result
35
53
  ```
36
54
 
55
+ ## Platform Support
56
+
57
+ ✅ = Current | 🔧 = Possible (recompile/rebuild needed) | ❌ = Not feasible
58
+
59
+ | Platform | msger (Rust/wry) | msgview (Electron) | msga (.NET MAUI) |
60
+ |----------|:----------------:|:------------------:|:----------------:|
61
+ | **Windows** | ✅ | ✅ | ✅ |
62
+ | **Linux** | ✅ | ✅ | ❌ |
63
+ | **macOS** | 🔧 wry supports it | ✅ | 🔧 Mac Catalyst |
64
+ | **Raspberry Pi** | ⚠️ runs but no display | ✅ | ❌ |
65
+ | **Android** | ❌ | ❌ | ✅ (primary) |
66
+ | **iOS** | ❌ | ❌ | ❌ |
67
+
68
+ > **msger** uses wry (Rust webview). Desktop platforms (Win/Linux/Mac) are mature. msger can cross-compile and run on the Pi but does not display properly (wry/GTK rendering issues) — not a priority to fix.
69
+ > **msgview** runs wherever Electron does — desktop only, no mobile.
70
+ > **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.
71
+
72
+ ## Implementation Status
73
+
74
+ ✅ = Implemented | ⚠️ = Partial | ❌ = Not implemented
75
+
76
+ | API | msgview | msger | msga | Notes |
77
+ |-----|:-------:|:-----:|:----:|-------|
78
+ | **Configuration** | | | | |
79
+ | `version` | ❌ | ❌ | ✅ | `window.msgapi.version` string |
80
+ | `setLogging(value?)` | ❌ | ❌ | ✅ | Returns previous state |
81
+ | `setAutoUpdate(value?)` | ❌ | ❌ | ✅ | `'auto'`/`'check'`/`'off'`; msga Android only |
82
+ | **Window Control** | | | | **`window.msgapi.*`** |
83
+ | `toggleFullscreen()` | ❌ | ✅ | ✅ | msger: via `window.msgapi` (msger-api.js) |
84
+ | `setFullscreen(bool)` | ✅ | ✅ | ✅ | |
85
+ | `minimize()` | ✅ | ✅ | ✅ | msga: Windows only |
86
+ | `maximize()` | ✅ | ✅ | ✅ | msga: Windows only |
87
+ | `setSize(w, h)` | ✅ | ✅ | ✅ | msga: Windows only |
88
+ | `setPosition(x, y)` | ✅ | ✅ | ✅ | msga: Windows only |
89
+ | `setAlwaysOnTop(bool)` | ✅ | ✅ | ❌ | |
90
+ | `close(result?)` | ✅ | ✅ | ✅ | |
91
+ | `navigate(url)` | ❌ | ❌ | ✅ | msga only |
92
+ | `reload()` | ❌ | ❌ | ✅ | msga only |
93
+ | **File System** | | | | **`window.msgapi.fs.*`** |
94
+ | `fs.selectFile()` | ❌ | ❌ | ✅ | msga: full JS bridge wired |
95
+ | `fs.selectFiles()` | ❌ | ❌ | ✅ | msga: full JS bridge wired |
96
+ | `fs.saveFileAs()` | ❌ | ❌ | ✅ | msga: Windows only |
97
+ | `fs.selectFolder()` | ❌ | ❌ | ✅ | msga: Windows only |
98
+ | `fs.read(path)` | ❌ | ❌ | ✅ | msga: binary→base64→TextDecoder |
99
+ | `fs.readAsDataUrl(path)` | ❌ | ❌ | ✅ | msga: auto-detects MIME from extension |
100
+ | `fs.write(path, content)` | ❌ | ❌ | ✅ | |
101
+ | `fs.list(path)` | ❌ | ❌ | ✅ | msga: includes attributes/created on Windows |
102
+ | `fs.exists(path)` | ❌ | ❌ | ✅ | |
103
+ | `fs.delete(path)` | ❌ | ❌ | ✅ | |
104
+ | **Shell** | | | | **`window.msgapi.shell.*`** |
105
+ | `shell.exec()` | ❌ | ❌ | ✅ | Process with stdout/stderr; limited on non-rooted Android |
106
+ | `shell.open()` | ❌ | ❌ | ✅ | Android: Intent.ACTION_VIEW; Windows: ShellExecute |
107
+ | `shell.showInFolder()` | ❌ | ❌ | ❌ | Defined in interface, not yet implemented |
108
+ | `shell.trash()` | ❌ | ❌ | ❌ | Defined in interface, not yet implemented |
109
+ | **UDP Networking** | | | | **`window.msgapi.udp.*`** |
110
+ | `udp.send()` | ✅ | ✅ | ✅ | Node dgram / Rust UdpSocket / C# UdpClient |
111
+ | `udp.listen()` | ✅ | ✅ | ✅ | Background receive loop in all hosts |
112
+ | `udp.sendReceive()` | ✅ | ✅ | ✅ | Send + wait with timeout |
113
+ | `udp.broadcast()` | ✅ | ✅ | ✅ | 255.255.255.255 broadcast |
114
+ | **HTTP Fetch** | | | | **`window.msgapi.http.*`** |
115
+ | `http.fetch()` | ✅ | ✅ | ✅ | Native HTTP — bypasses CORS/mixed-content |
116
+
117
+ > **msger** now injects `window.msgapi` via `msger-api.js` with window control, UDP, and HTTP. File system and shell are not yet implemented in msger or msgview.
118
+
119
+ [httpudp-client](../../homecontrol/utils/udp/httpudp-client/README.md) auto-detects `window.msgapi?.udp` and uses native UDP when available, falling back to the httpudp WebSocket proxy otherwise.
120
+
121
+ See [`msgapi-plan.md`](./msgapi-plan.md) for the full implementation plan and roadmap.
122
+
37
123
  ## API Reference
38
124
 
125
+ ### Configuration
126
+
127
+ - `version` - Host app version string (e.g., `"1.2.0"`)
128
+ - `setLogging(value?)` - Control debug logging. Returns previous state as string.
129
+ - `'msga'` - C# host logging only (logit-msga)
130
+ - `'msgapi'` - JS bridge logging only (logit-msgapi)
131
+ - `'on'` - Both streams
132
+ - `'off'` / `'none'` - All off
133
+ - `null` / `''` - Query current state without changing
134
+ - Returns: `'msga'`, `'msgapi'`, `'msga,msgapi'`, or `'none'`
135
+ - `setAutoUpdate(value?)` - Control APK auto-update behavior. Returns previous state as string. msga Android only.
136
+ - `'auto'` - Check versions.json, download + install silently (no prompt)
137
+ - `'check'` - Check versions.json, prompt "Install now?" before downloading
138
+ - `'off'` - No update checking
139
+ - `null` / `undefined` - Query current state without changing
140
+ - Default: `'auto'` (configurable via `AUTO_UPDATE` in msga.env)
141
+ - Checks on startup then every `UPDATE_INTERVAL_MINUTES` (default 60)
142
+
39
143
  ### Window Control
40
144
 
41
145
  - `toggleFullscreen()` - Toggle fullscreen mode
@@ -49,15 +153,62 @@ window.close(); // Instead of msgapi.close() if not passing result
49
153
 
50
154
  ### File System
51
155
 
156
+ File dialogs (user picks interactively — safe without special permissions):
52
157
  - `fs.selectFile(options?)` - Open file picker
53
158
  - `fs.selectFiles(options?)` - Open multi-file picker
54
159
  - `fs.saveFileAs(content, filename?, options?)` - Save file dialog
55
160
  - `fs.selectFolder(options?)` - Folder picker
56
- - `fs.read(path)` - Read file (requires allowFs flag)
57
- - `fs.write(path, content)` - Write file (requires allowFs flag)
58
- - `fs.list(path)` - List directory (requires allowFs flag)
59
- - `fs.exists(path)` - Check if file exists (requires allowFs flag)
60
- - `fs.delete(path)` - Delete file (requires allowFs flag)
161
+
162
+ Direct path operations (give the webview full filesystem access — msga only currently):
163
+ - `fs.read(path, options?)` - Read file. Options: `{ encoding: 'utf8' | 'base64' | 'binary' }`. Default `'utf8'` returns string; `'base64'` returns raw base64 string (efficient for images); `'binary'` returns `Uint8Array`.
164
+ - `fs.readAsDataUrl(path, mimeType?)` - Read file as data URL (`data:image/jpeg;base64,...`). Auto-detects MIME from extension if omitted.
165
+ - `fs.write(path, content)` - Write file
166
+ - `fs.list(path)` - List directory. Returns `FileInfo[]` with `attributes` (Win32 flags) and `created` on Windows.
167
+ - `fs.exists(path)` - Check if file/directory exists
168
+ - `fs.delete(path)` - Delete file or directory
169
+
170
+ > **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.
171
+
172
+ ### UDP Networking
173
+
174
+ Native UDP socket access — no WebSocket proxy needed when running inside a msgapi host.
175
+
176
+ - `udp.send(host, port, data)` - Send UDP packet to specific host
177
+ - `udp.listen(port, callback)` - Listen for incoming UDP packets (returns `{close()}` handle)
178
+ - `udp.sendReceive(host, port, data, timeoutMs?)` - Send and wait for response
179
+ - `udp.broadcast(port, data)` - Broadcast to local network
180
+
181
+ ### Shell / Process Execution
182
+
183
+ Native process execution — run commands, open files with default handler. msga only currently.
184
+
185
+ - `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.
186
+ - `shell.open(path)` - Open file/URL with system default handler (Windows: ShellExecute, Android: Intent.ACTION_VIEW)
187
+ - `shell.showInFolder(path)` - Select file in Explorer / file manager (not yet implemented)
188
+ - `shell.trash(path)` - Move to Recycle Bin / Trash (not yet implemented)
189
+
190
+ ### HTTP Fetch
191
+
192
+ 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).
193
+
194
+ - `http.fetch(url, init?)` - Drop-in replacement for browser `fetch()`. Same `RequestInit` params, returns a real `Response` object (`.ok`, `.status`, `.json()`, `.text()`, `.headers`, `.clone()`, etc.)
195
+
196
+ ```javascript
197
+ // HTTP from an HTTPS page — blocked by browsers, works via msgapi
198
+ const r = await window.msgapi.http.fetch('http://192.168.1.5/api/status');
199
+ const data = await r.json();
200
+
201
+ // POST with headers and body
202
+ const r2 = await window.msgapi.http.fetch('http://192.168.1.5/api/cmd', {
203
+ method: 'POST',
204
+ headers: { 'Content-Type': 'application/json' },
205
+ body: JSON.stringify({ cmd: 'reboot' })
206
+ });
207
+ ```
208
+
209
+ **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.
210
+
211
+ **Unsupported `init` fields** (silently ignored — not meaningful for native HTTP): `mode`, `credentials`, `cache`, `signal`, `redirect`. `new Headers()` objects are normalized to plain objects.
61
212
 
62
213
  ## Examples
63
214
 
@@ -86,10 +237,49 @@ The samples demonstrate:
86
237
  - And more!
87
238
 
88
239
 
240
+ ## Cross-Platform Implementation
241
+
242
+ `msgapidefs` defines the TypeScript interface only. Each host implements the `window.msgapi` bridge differently:
243
+
244
+ ### msgview (Electron)
245
+ - **Bridge:** Electron `contextBridge.exposeInMainWorld('msgapi', ...)` in `preload/preload.ts`
246
+ - **Mechanism:** `ipcRenderer.invoke()` calls to main process IPC handlers
247
+ - **Window control:** Electron `BrowserWindow` API
248
+ - **File system:** Node.js `fs` module via IPC, file dialogs via Electron `dialog`
249
+
250
+ ### msger (Rust/wry)
251
+ - **Bridge:** JavaScript injected via wry `with_initialization_script()` in `msger-native/src/main.rs`
252
+ - **JS API:** `msger-native/src/msger-api.js` creates `window.msgapi` object
253
+ - **Mechanism:** `window.ipc.postMessage(JSON.stringify(...))` to Rust IPC handler
254
+ - **Capabilities:** Window control, UDP, HTTP fetch via `window.msgapi`
255
+ - **Limitations:** wry 0.47.2 has IPC issues; `close()` uses `window.close()` workaround
256
+
257
+ ### msga (.NET MAUI)
258
+ - **Bridge:** JavaScript injected via `WebViewControl.EvaluateJavaScriptAsync()` in `MainPage.xaml.cs`
259
+ - **JS API:** `GetMsgApiScript()` creates `window.msgapi` object inline
260
+ - **Mechanism:** `msgapi://` URL interception — JS creates hidden iframes, C# intercepts in `OnNavigating`
261
+ - **Async:** Promise callbacks via `callNativeAsync()` → `window._msgapiResolve()` / `_msgapiReject()`
262
+ - **Window control:** Platform-conditional (`#if WINDOWS` uses WinUI AppWindow APIs)
263
+ - **File system:** MAUI `FilePicker`, Windows-specific `FileSavePicker`/`FolderPicker`
264
+ - **Platforms:** Android (primary target), Windows (development/testing)
265
+
266
+ ### Adding New Sub-Objects
267
+
268
+ Each capability is an optional sub-object on `MsgAPI` (e.g., `fs?`, `udp?`). To add a new one:
269
+ 1. Define the interface in [`msgapidefs.ts`](./msgapidefs.ts) as an optional sub-object on `MsgAPI`
270
+ 2. Implement the native side in each host:
271
+ - **msgview:** Add IPC handlers in `main.ts`, expose in `preload/preload.ts`
272
+ - **msger:** Add Rust handler in `main.rs`, expose in `msger-api.js`
273
+ - **msga:** Add C# methods + JS injection in `GetMsgApiScript()` (see [msga readme](../msga/readme.md))
274
+ 3. Browser code uses optional chaining: `window.msgapi?.newFeature?.method()` — works everywhere, undefined when unavailable
275
+
276
+ See [`msgapi-plan.md`](./msgapi-plan.md) for the full feature matrix and implementation status across all three hosts.
277
+
89
278
  ## Related Packages
90
279
 
91
280
  - [@bobfrankston/msgview](https://www.npmjs.com/package/@bobfrankston/msgview) - Electron-based message box (slower, works on Pi)
92
281
  - [@bobfrankston/msger](https://www.npmjs.com/package/@bobfrankston/msger) - Rust/wry-based message box (fast, lightweight)
282
+ - [msga](https://github.com/BobFrankston/msga) - .NET MAUI Android/Windows wrapper app
93
283
 
94
284
  ## License
95
285
 
package/msgapi-plan.md CHANGED
@@ -1,10 +1,11 @@
1
1
  # msgapi Implementation Plan
2
2
 
3
+ > **This is the authoritative plan for msgapi.** Other TODOs (md/todo.md) reference here for msgapi-related items.
3
4
  Shared planning document for msgapi across implementations:
4
- - **TypeScript**: msgapidefs (this repo) - types/definitions
5
- - **TypeScript/Electron**: msgview
6
- - **Rust/wry**: msger
7
- - **C#/.NET MAUI**: msga (y:\dev\utils\msga)
5
+ - **TypeScript**: [msgapidefs](../msgapidefs/) (this repo) - types/definitions ([README](../msgapidefs/README.md))
6
+ - **TypeScript/Electron**: [msgview](../msgview/)
7
+ - **Rust/wry**: [msger](../msger/)
8
+ - **C#/.NET MAUI**: [msga](../msga/) ([readme](../msga/readme.md))
8
9
 
9
10
  ## Current Status
10
11
 
@@ -13,72 +14,144 @@ Shared planning document for msgapi across implementations:
13
14
  | API | msgview | msger | msga | Notes |
14
15
  |-----|---------|-------|------|-------|
15
16
  | **Window Control** |
16
- | toggleFullscreen() | | ✅ | ⚠️ | msga: C# exists, not wired |
17
- | setFullscreen(bool) | ✅ | ✅ | ⚠️ | msga: C# exists, not wired |
18
- | minimize() | ✅ | ✅ | ⚠️ | msga: Windows only in C# |
19
- | maximize() | ✅ | ✅ | ⚠️ | msga: Windows only in C# |
20
- | setSize(w,h) | ✅ | ✅ | ⚠️ | msga: Windows only in C# |
21
- | setPosition(x,y) | ✅ | ✅ | ⚠️ | msga: Windows only in C# |
17
+ | toggleFullscreen() | | ✅ | | msga: fire-and-forget JS→C# |
18
+ | setFullscreen(bool) | ✅ | ✅ | | |
19
+ | minimize() | ✅ | ✅ | | msga: Windows only |
20
+ | maximize() | ✅ | ✅ | | msga: Windows only |
21
+ | setSize(w,h) | ✅ | ✅ | | msga: Windows only |
22
+ | setPosition(x,y) | ✅ | ✅ | | msga: Windows only |
22
23
  | setAlwaysOnTop(bool) | ✅ | ✅ | ❌ | |
23
- | close(result?) | ✅ | ✅ | ⚠️ | msga: basic close only |
24
+ | close(result?) | ✅ | ✅ | | |
25
+ | navigate(url) | ❌ | ❌ | ✅ | msga only |
26
+ | reload() | ❌ | ❌ | ✅ | msga only |
24
27
  | **File System** |
25
- | fs.selectFile() | | | ⚠️ | msga: C# exists, not wired |
26
- | fs.selectFiles() | | | ⚠️ | msga: C# exists, not wired |
27
- | fs.saveFileAs() | | | | |
28
- | fs.selectFolder() | | | | |
29
- | fs.read(path) | | | | Requires allowFs flag |
30
- | fs.write(path) | | | | Requires allowFs flag |
31
- | fs.list(path) | | | | Requires allowFs flag |
32
- | fs.exists(path) | | | | Requires allowFs flag |
33
- | fs.delete(path) | | | | Requires allowFs flag |
34
-
35
- Legend: ✅ Implemented | ⚠️ Partial/Not wired | ❌ Not implemented
28
+ | fs.selectFile() | | | | msga: full JS bridge wired |
29
+ | fs.selectFiles() | | | | msga: full JS bridge wired |
30
+ | fs.saveFileAs() | | | | msga: Windows only |
31
+ | fs.selectFolder() | | | | msga: Windows only |
32
+ | fs.read(path, options?) | | | | encoding: utf8/base64/binary |
33
+ | fs.readAsDataUrl(path) | | | | Auto-detects MIME from extension |
34
+ | fs.write(path) | | | | |
35
+ | fs.list(path) | | | | Includes attributes/created on Windows |
36
+ | fs.exists(path) | | | | |
37
+ | fs.delete(path) | ❌ | ❌ | ✅ | |
38
+ | **Shell** |
39
+ | shell.exec() | ❌ | ❌ | ✅ | Process with stdout/stderr; limited on non-rooted Android |
40
+ | shell.open() | ❌ | ❌ | ✅ | Android: Intent.ACTION_VIEW; Windows: ShellExecute |
41
+ | shell.showInFolder() | ❌ | ❌ | ❌ | Defined in interface, not yet implemented |
42
+ | shell.trash() | ❌ | ❌ | ❌ | Defined in interface, not yet implemented |
43
+ | **UDP** |
44
+ | udp.send() | ✅ | ✅ | ✅ | Node dgram / Rust UdpSocket / C# UdpClient |
45
+ | udp.listen() | ✅ | ✅ | ✅ | Background receive loop in all hosts |
46
+ | udp.sendReceive() | ✅ | ✅ | ✅ | Send + wait with timeout |
47
+ | udp.broadcast() | ✅ | ✅ | ✅ | 255.255.255.255 broadcast |
48
+ | **HTTP** |
49
+ | http.fetch() | ✅ | ✅ | ✅ | CORS/mixed-content bypass via native HTTP; returns real Response object |
50
+ | **Configuration** |
51
+ | setLogging(value?, name?) | ❌ | ❌ | ✅ | Returns old state; 'msga'/'msgapi'/'on'/'off'/null; name included in log messages/filenames |
52
+ | setAutoUpdate(value?) | ❌ | ❌ | ✅ | 'auto'/'check'/'off'; defaults to 'off' (web app owns policy) |
53
+ | installUpdate(url) | ❌ | ❌ | ✅ | Download + install APK; Android only. Web app calls this. |
54
+ | getNetworkInfo() | ❌ | ❌ | ✅ | Fresh local IP + subnet mask; used by httpudp-client isAddressReachable() |
55
+ | info | ❌ | ❌ | ✅ | `MsgHostInfo`: host, version, platform, updateUrl |
56
+ | updateUrl | ❌ | ❌ | ✅ | URL to versions.json — install source for update checks |
57
+ | version | ❌ | ❌ | ✅ | `window.msgapi.version` = app version string |
58
+
59
+ Legend: ✅ Implemented | ❌ Not implemented
60
+
61
+ ### msga Logging
62
+
63
+ Two independent log streams, both via `https://rmf39.aaz.lt/logit/`:
64
+
65
+ | Stream | Log name | Controlled by | Content |
66
+ |--------|----------|---------------|---------|
67
+ | **C# (MsgALog)** | `logit-msga` | `LOGGING=false` in msga.env | OnNavigated, msgapi:// dispatch, SendAsync, UDP recv |
68
+ | **JS (_logit)** | `logit-msgapi` | `JS_LOGGING=false` in msga.env | msgapi inject, msgapi object creation |
69
+
70
+ **Startup defaults** are `false` in `msga.env` — logging is off unless the web app enables it. At **runtime**:
71
+ ```javascript
72
+ msgapi.setLogging() // → 'none' (query current, no change)
73
+ msgapi.setLogging('on') // enables both C# and JS logging
74
+ msgapi.setLogging('on', 'test') // enables both; C# logs to logit-msga-test, JS to logit-msgapi-test, 'test' in messages
75
+ msgapi.setLogging('msga') // enables C# only
76
+ msgapi.setLogging('msgapi') // enables JS only
77
+ msgapi.setLogging('off') // disables both
78
+ ```
79
+ Always returns previous state as string: `'msga'`, `'msgapi'`, `'msga,msgapi'`, or `'none'`.
80
+ The optional `name` parameter is included in: log file names (`logit-msga-{name}`, `logit-msgapi-{name}`) and C# log message prefix.
36
81
 
37
- ### msga Wiring Status
82
+ ### msga Implementation Status
38
83
 
39
- The MsgApiBridge.cs file in msga has C# implementations but the JavaScript bridge is not connected:
40
- - JavaScript injection exists in MainPage.xaml.cs
41
- - C# handler methods exist in Services/MsgApiBridge.cs
42
- - Missing: WebView JavaScript-to-C# message handler wiring
84
+ msga has the most complete msgapi.udp implementation. The JS bridge is fully wired:
85
+ - `GetMsgApiScript()` in MainPage.xaml.cs injects `window.msgapi` with all sub-objects
86
+ - Window control: fire-and-forget calls via `MsgABridge.invokeMethod()`
87
+ - UDP: async Promise-based calls via `msgapi://udp/*` URL routing → `UdpService.cs`
88
+ - Data transport: base64 encoding for binary data over the bridge
89
+ - Listener callbacks: pushed via `window._msgapiUdpReceive()` from C#
43
90
 
44
91
  ---
45
92
 
46
- ## Planned Features
47
-
48
- ### 1. UDP Communication
93
+ ## UDP Interface
49
94
 
50
- **Use case**: Local device control, IoT messaging, low-latency communication
95
+ Defined in [`msgapidefs.ts`](./msgapidefs.ts) and implemented in [msga](../msga/). Types available via `@bobfrankston/msgapidefs`.
51
96
 
52
97
  ```typescript
53
98
  interface MsgAPI {
54
- udp: {
55
- // Send UDP datagram
99
+ udp?: {
56
100
  send(host: string, port: number, data: string | Uint8Array): Promise<void>;
57
-
58
- // Listen for UDP datagrams on a port
59
101
  listen(port: number, callback: (data: Uint8Array, sender: {host: string, port: number}) => void): Promise<{close: () => void}>;
60
-
61
- // Send and wait for response (with timeout)
62
102
  sendReceive(host: string, port: number, data: string | Uint8Array, timeoutMs?: number): Promise<Uint8Array>;
63
-
64
- // Broadcast to subnet
65
103
  broadcast(port: number, data: string | Uint8Array): Promise<void>;
66
104
  }
67
105
  }
68
106
  ```
69
107
 
70
- **Implementation notes**:
71
- - TypeScript: Define types only (browser cannot do UDP)
72
- - Electron (msgview): Use dgram module
73
- - Rust (msger): Use std::net::UdpSocket or tokio UDP
74
- - C#/MAUI (msga): Use System.Net.Sockets.UdpClient
108
+ **Platform implementations (UDP)**:
109
+ - **msga** (C#/MAUI): `System.Net.Sockets.UdpClient` via `UdpService.cs`
110
+ - **msgview** (Electron): Node.js `dgram` via `ipcMain.handle` / preload
111
+ - **msger** (Rust/wry): `std::net::UdpSocket` via `NativeEvent` + event loop
75
112
 
76
- **Security considerations**:
77
- - Require explicit allowUdp flag or permission prompt
78
- - Consider port restrictions (no well-known ports < 1024?)
79
- - Localhost-only mode option
113
+ **Platform implementations (HTTP fetch)**:
114
+ - **msga** (C#/MAUI): `HttpClient` static instance, async `SendAsync`
115
+ - **msgview** (Electron): Node.js `fetch()` via `ipcMain.handle`
116
+ - **msger** (Rust/wry): ✅ `ureq` crate — blocking in `std::thread::spawn`, results via event loop
80
117
 
81
- ### 2. Enhanced File System
118
+ **httpudp-client integration**: `HttpUdpClient.connect()` detects `window.msgapi?.udp` and uses it directly when available, falling back to the httpudp WebSocket proxy otherwise. Callers never know which path was taken.
119
+
120
+ ---
121
+
122
+ ## Web-App-Driven APK Updates
123
+
124
+ Update policy is owned by the web app (houser), not msga. msga provides the `installUpdate(url)` primitive; the web app decides when/whether to use it.
125
+
126
+ **How it works:**
127
+ 1. Web app fetches `versions.json` from `https://rmf39.aaz.lt/msga/versions.json`
128
+ 2. Compares remote version with `window.msgapi.version`
129
+ 3. If newer, calls `await window.msgapi.installUpdate(apkUrl)` to download + install
130
+ 4. Optionally, UDP broadcast can notify about available updates (no polling needed)
131
+
132
+ **`setAutoUpdate` defaults to `'off'`** — the C#-side timer-based auto-update is disabled by default. Web app calls `installUpdate()` directly when it decides an update is needed. `setAutoUpdate('auto')` can still be used to re-enable C#-side silent updates if desired.
133
+
134
+ ```javascript
135
+ // Web app update check example (types: MsgVersionInfo, MsgApkInfo from msgapidefs)
136
+ // Versions are strings like "1.5.21" — compare numerically, not lexicographically
137
+ const isNewer = (remote, current) => {
138
+ const c = current.split('.').map(Number);
139
+ return remote.split('.').map(Number).map((v, i) => v - c[i]).reduce((r, d) => r || d, 0) > 0;
140
+ };
141
+ const resp = await fetch(window.msgapi.updateUrl);
142
+ const info = await resp.json(); // MsgVersionInfo
143
+ if (isNewer(info.version, window.msgapi.version)) {
144
+ const apk = info.apks.find(a => a.name === 'houser45');
145
+ const baseUrl = window.msgapi.updateUrl.replace(/\/[^/]*$/, '/');
146
+ await window.msgapi.installUpdate(baseUrl + apk.file);
147
+ }
148
+ ```
149
+
150
+ ---
151
+
152
+ ## Planned Features
153
+
154
+ ### 1. Enhanced File System
82
155
 
83
156
  **Use case**: Full file system access for local apps, document management
84
157
 
@@ -160,26 +233,33 @@ interface MsgAPI {
160
233
 
161
234
  ## Implementation Priorities
162
235
 
163
- ### Phase 1: Wire Up msga (Current)
164
- 1. Connect JavaScript bridge to C# handlers in msga
165
- 2. Test existing window control APIs on Android/Windows
166
- 3. Ensure parity with msgview/msger for basic features
167
-
168
- ### Phase 2: UDP Support
169
- 1. Add UDP types to msgapidefs
170
- 2. Implement in msger (Rust) - fastest iteration
171
- 3. Port to msgview (Electron)
172
- 4. Port to msga (C#/MAUI)
173
-
174
- ### Phase 3: Enhanced File System
175
- 1. Add new fs types to msgapidefs
176
- 2. Implement mkdir, rename, copy, stat
177
- 3. Add file watching (platform-specific)
178
-
179
- ### Phase 4: System Integration
236
+ ### Phase 1: msga msgapi + UDP ✅ Done
237
+ 1. JavaScript bridge wired in msga (GetMsgApiScript → msgapi:// URL routing)
238
+ 2. Window control working on Android/Windows
239
+ 3. UDP types added to msgapidefs
240
+ 4. ✅ UDP fully implemented in msga (UdpService.cs)
241
+ 5. httpudp-client detects msgapi.udp and uses it natively
242
+
243
+ ### Phase 2: UDP in msger/msgview Done
244
+ 1. msger (Rust) — `std::net::UdpSocket` via NativeEvent + thread::spawn
245
+ 2. msgview (Electron) — Node.js `dgram` via ipcMain.handle + preload
246
+
247
+ ### Phase 3: File System + Shell in msga ✅ Done
248
+ 1. fs.read with encoding options (utf8/base64/binary)
249
+ 2. fs.readAsDataUrl with MIME auto-detection
250
+ 3. fs.list with attributes/created on Windows
251
+ 4. ✅ shell.exec — process execution with stdout/stderr capture
252
+ 5. shell.open open files/URLs with system default handler
253
+
254
+ ### Phase 4: File System + Shell in msger/msgview
255
+ 1. Implement fs.* via msgcommon shared code
256
+ 2. Implement shell.exec/open via msgcommon shared code
257
+ 3. Add mkdir, rename, copy, stat extensions
258
+
259
+ ### Phase 5: System Integration (Lower Priority)
180
260
  1. Clipboard support
181
261
  2. Notifications
182
- 3. Platform detection
262
+ 3. shell.showInFolder, shell.trash
183
263
 
184
264
  ---
185
265
 
@@ -200,6 +280,24 @@ Test categories:
200
280
 
201
281
  ---
202
282
 
283
+ ## Known Issues (Resolved)
284
+
285
+ ### msga: hash navigations killed UDP listener (fixed v1.4.0)
286
+
287
+ MAUI fires OnNavigated for in-page hash changes (#Floor3, #Office, etc.) even though it's a SPA. OnNavigated was disposing UdpService on every such event, killing the listener that httpudp-client set up via msgapi.udp.listen(). Sends still worked (ephemeral sockets) but responses were lost. Symptom: "works once then stops" as the first room switch killed the listener.
288
+
289
+ **Fix:** OnNavigated now compares the base URL (without hash) and skips disposal + re-injection for hash-only changes.
290
+
291
+ ### msga: httpudp://start caused stuck loading overlay (fixed v1.5.3)
292
+
293
+ httpudp-client does `location.href = 'httpudp://start'` to try launching the httpudp proxy app on Android. This custom URL scheme triggered MAUI navigation events in unexpected order (OnNavigated before OnNavigating), causing the loading overlay to get stuck and — in some cases — preventing msgapi injection entirely.
294
+
295
+ **Fix (v1.5.3):** OnNavigating and OnNavigated now skip non-http/https URLs via `IsHttpUrl()` check. LinkNavigationWebViewClient blocks non-http schemes (`return true` = "handled") so the Android WebView doesn't try to load them.
296
+
297
+ **Note:** httpudp-client's `ensureHttpUdpProxy()` should NOT fire when running inside msga (msgapi.udp is available natively). If `httpudp://start` navigations appear in logs, it means httpudp-client isn't detecting msgapi.udp — check that `msgapiready` event fires before httpudp-client's `connect()` runs.
298
+
299
+ ---
300
+
203
301
  ## Notes
204
302
 
205
303
  - msgapi is injected as `window.msgapi` global
package/msgapidefs.ts CHANGED
@@ -1,263 +1,152 @@
1
- /**
2
- * TypeScript definitions for the msgapi JavaScript API
3
- *
4
- * This module provides type-safe access to msgapi's window control,
5
- * data persistence, and file system features (used by msgview and msger).
6
- *
7
- * See README.md for complete usage documentation and examples.
8
- */
9
-
10
- /**
11
- * Result object returned when the window closes
12
- */
13
- export interface MsgResult {
14
- /** Button that was clicked */
15
- button: string;
16
- /** Optional input field value */
17
- value?: string;
18
- /** Optional form data */
19
- form?: Record<string, any>;
20
- /** True if window was closed via close button */
21
- closed?: boolean;
22
- /** True if window was dismissed via Escape key */
23
- dismissed?: boolean;
24
- /** True if window was closed due to timeout */
25
- timeout?: boolean;
1
+ // TypeScript definitions for the msgapi JavaScript API
2
+ // Type-safe access to msgapi's window control, file system, shell, and UDP features
3
+ // Used by msgview, msger, and msga. See README.md for docs.
4
+ //
5
+ // ⚠️ SECURITY: msgapi gives full native OS access (filesystem, processes, networking).
6
+ // Intended ONLY for trusted apps — native apps written as web pages. Do not expose to untrusted content.
7
+ // ⚠️ EXPERIMENTAL: APIs are experimental and subject to change.
8
+
9
+ export interface MsgResult { // Result object returned when the window closes
10
+ button: string; // Button that was clicked
11
+ value?: string; // Optional input field value
12
+ form?: Record<string, any>; // Optional form data
13
+ closed?: boolean; // True if closed via close button
14
+ dismissed?: boolean; // True if dismissed via Escape key
15
+ timeout?: boolean; // True if closed due to timeout
26
16
  }
27
17
 
28
- /**
29
- * File information returned by file system operations
30
- */
31
- export interface FileInfo {
32
- /** File name */
33
- name: string;
34
- /** Full file path */
35
- path: string;
36
- /** True if this is a directory */
37
- isDir: boolean;
38
- /** File size in bytes (0 for directories) */
39
- size: number;
40
- /** Last modified timestamp (ISO 8601 string) */
41
- modified?: string;
18
+ export interface FileInfo { // File information from file system operations
19
+ name: string; // File name
20
+ path: string; // Full file path
21
+ isDir: boolean; // True if this is a directory
22
+ size: number; // File size in bytes (0 for directories)
23
+ modified?: string; // Last modified timestamp (ISO 8601 string)
24
+ created?: string; // Creation time (ISO 8601 string); undefined on platforms without creation time
25
+ attributes?: number; // Win32 FILE_ATTRIBUTE_* flags (e.g. 0x00400000 = RECALL_ON_DATA_ACCESS for OneDrive cloud-only); undefined on Android/Linux
42
26
  }
43
27
 
44
- /**
45
- * Options for file selection dialogs
46
- */
47
- export interface FileDialogOptions {
48
- /** Dialog title */
49
- title?: string;
50
- /** Default filename for save dialogs */
51
- defaultFilename?: string;
52
- /** File filters (e.g., [{name: "Text", extensions: ["txt", "md"]}]) */
53
- filters?: Array<{name: string; extensions: string[]}>;
54
- /** Default directory to open */
55
- defaultPath?: string;
28
+ export interface FileDialogOptions { // Options for file selection dialogs
29
+ title?: string; // Dialog title
30
+ defaultFilename?: string; // Default filename for save dialogs
31
+ filters?: Array<{name: string; extensions: string[]}>; // File filters
32
+ defaultPath?: string; // Default directory to open
56
33
  }
57
34
 
58
- /**
59
- * Selected file with content
60
- */
61
- export interface SelectedFile {
62
- /** File name */
63
- name: string;
64
- /** Full file path */
65
- path: string;
66
- /** File content as string */
67
- content: string;
35
+ export interface SelectedFile { // Selected file with content
36
+ name: string; // File name
37
+ path: string; // Full file path
38
+ content: string; // File content as string
68
39
  }
69
40
 
70
- /**
71
- * msgapi JavaScript API
72
- *
73
- * Available when running inside msgview/msger via window.msgapi
74
- */
75
- export interface MsgAPI {
76
- // ========================================
77
- // Window Control
78
- // ========================================
41
+ export type MsgHost = 'msga' | 'msger' | 'msgview';
42
+ export type MsgPlatform = 'android' | 'windows' | 'linux' | 'macos';
43
+ export type AutoUpdateMode = 'auto' | 'check' | 'off';
44
+ export type LoggingMode = 'msga' | 'msgapi' | 'on' | 'off' | 'none';
45
+
46
+ export interface MsgHostInfo { // Host application identity and capabilities
47
+ host: MsgHost; // Which host app is running
48
+ version: string; // App version (e.g. "1.5.21") — semver string, compare numerically not lexicographically
49
+ platform: MsgPlatform; // Runtime platform
50
+ appName?: string; // APK variant name (e.g. "houser46") — derived from package ID, matches versions.json apks[].name
51
+ updateUrl?: string; // URL to versions.json — install source for update checks (msga only)
52
+ apkUrl?: string; // Direct URL to this APK (e.g. "https://rmf39.aaz.lt/msga/apks/houser46.apk")
53
+ localIp?: string; // Device IP on current network (e.g. "172.20.1.42")
54
+ localMask?: string; // Subnet mask (e.g. "255.255.0.0") — msga uses ip+mask to reject udp.send() when destination is off-network
55
+ }
79
56
 
80
- /**
81
- * Toggle fullscreen mode on/off
82
- * @example
83
- * msgapi.toggleFullscreen();
84
- */
85
- toggleFullscreen(): void;
57
+ export interface MsgApkInfo { // One APK entry in versions.json
58
+ name: string; // App variant name (e.g. "houser", "houser45")
59
+ url: string; // Default URL the APK loads
60
+ description: string; // Human-readable description
61
+ icon: string; // Icon URL
62
+ file: string; // APK file path relative to versions.json (e.g. "apks/houser.apk")
63
+ sizeMB: number; // Approximate size in MB
64
+ built: string; // Build timestamp (ISO 8601)
65
+ default?: boolean; // True if this is the default APK
66
+ }
86
67
 
87
- /**
88
- * Set fullscreen mode
89
- * @param enabled - true to enter fullscreen, false to exit
90
- * @example
91
- * msgapi.setFullscreen(true);
92
- */
93
- setFullscreen(enabled: boolean): void;
68
+ export interface MsgVersionInfo { // Structure of versions.json
69
+ version: string; // Latest available version (e.g. "1.5.21") — semver string, compare numerically not lexicographically
70
+ buildDate: string; // Build timestamp (ISO 8601)
71
+ apks: MsgApkInfo[]; // Available APK variants
72
+ }
94
73
 
95
- /**
96
- * Minimize the window
97
- * @example
98
- * msgapi.minimize();
99
- */
100
- minimize(): void;
74
+ // msgapi JavaScript API — available via window.msgapi inside msgview/msger/msga
75
+ export interface MsgAPI {
101
76
 
102
- /**
103
- * Toggle maximize/restore window
104
- * @example
105
- * msgapi.maximize();
106
- */
107
- maximize(): void;
77
+ version?: string; // Host app version (e.g. "1.3.0") — semver string, compare numerically not lexicographically
78
+ updateUrl?: string; // URL to versions.json — install source for update checks
79
+ info?: MsgHostInfo; // Host identity: host name, version, platform, updateUrl
108
80
 
109
- /**
110
- * Set window size
111
- * @param width - Window width in pixels
112
- * @param height - Window height in pixels
113
- * @example
114
- * msgapi.setSize(800, 600);
115
- */
116
- setSize(width: number, height: number): void;
81
+ // ── Configuration ────────────────────────────────────────────
117
82
 
118
- /**
119
- * Set window position
120
- * @param x - X coordinate in pixels
121
- * @param y - Y coordinate in pixels
122
- * @example
123
- * msgapi.setPosition(100, 100);
124
- */
125
- setPosition(x: number, y: number): void;
83
+ setLogging?(value?: LoggingMode, name?: string): LoggingMode; // null/undefined = query only. name included in log messages/filenames. Returns previous state.
84
+ setAutoUpdate?(value?: AutoUpdateMode): AutoUpdateMode; // null/undefined = query only. Returns previous state. msga Android only.
85
+ installUpdate?(url: string): Promise<void>; // Download + install APK from url. msga Android only; web app owns update policy.
86
+ getNetworkInfo?(): Promise<{ localIp: string; localMask: string }>; // Current device IP + subnet mask (fresh, not cached)
126
87
 
127
- /**
128
- * Set always-on-top behavior
129
- * @param enabled - true to keep window on top
130
- * @example
131
- * msgapi.setAlwaysOnTop(true);
132
- */
133
- setAlwaysOnTop(enabled: boolean): void;
88
+ // ── Window Control ──────────────────────────────────────────
134
89
 
135
- /**
136
- * Close the window and return result to parent process
137
- * @param result - Optional result to return to parent process
138
- * @example
139
- * // Close with default dismissed result
140
- * msgapi.close();
141
- *
142
- * // Close with custom result
143
- * msgapi.close({button: 'OK', value: 'user data'});
144
- */
145
- close(result?: Partial<MsgResult>): void;
90
+ toggleFullscreen(): void; // Toggle fullscreen mode on/off
91
+ setFullscreen(enabled: boolean): void; // Set fullscreen mode
92
+ minimize(): void; // Minimize the window
93
+ maximize(): void; // Toggle maximize/restore
94
+ setSize(width: number, height: number): void; // Set window size in pixels
95
+ setPosition(x: number, y: number): void; // Set window position in pixels
96
+ setAlwaysOnTop(enabled: boolean): void; // Keep window on top
97
+ close(result?: Partial<MsgResult>): void; // Close window, return result to parent
146
98
 
147
- // ========================================
148
- // File System Access
149
- // ========================================
99
+ // ── File System Access ──────────────────────────────────────
150
100
 
151
- /**
152
- * File system operations namespace
153
- */
154
101
  fs?: {
155
- /**
156
- * Select a file using native file picker
157
- * @param options - File dialog options
158
- * @returns Selected file with content, or null if cancelled
159
- * @example
160
- * const file = await msgapi.fs.selectFile({
161
- * title: 'Open File',
162
- * filters: [{name: 'Text', extensions: ['txt', 'md']}]
163
- * });
164
- * if (file) {
165
- * console.log(file.content);
166
- * }
167
- */
168
- selectFile(options?: FileDialogOptions): Promise<SelectedFile | null>;
169
-
170
- /**
171
- * Select multiple files using native file picker
172
- * @param options - File dialog options
173
- * @returns Array of selected files with content
174
- * @example
175
- * const files = await msgapi.fs.selectFiles({
176
- * title: 'Open Files'
177
- * });
178
- */
179
- selectFiles(options?: FileDialogOptions): Promise<SelectedFile[]>;
180
-
181
- /**
182
- * Save file using native save dialog
183
- * @param content - File content to save
184
- * @param defaultFilename - Default filename
185
- * @param options - Additional dialog options
186
- * @returns Path where file was saved, or null if cancelled
187
- * @example
188
- * const path = await msgapi.fs.saveFileAs('Hello World', 'greeting.txt');
189
- */
190
- saveFileAs(
191
- content: string,
192
- defaultFilename?: string,
193
- options?: FileDialogOptions
194
- ): Promise<string | null>;
195
-
196
- /**
197
- * Select a directory using native folder picker
198
- * @param options - Dialog options
199
- * @returns Selected directory path, or null if cancelled
200
- * @example
201
- * const dir = await msgapi.fs.selectFolder({
202
- * title: 'Select Output Folder'
203
- * });
204
- */
205
- selectFolder(options?: FileDialogOptions): Promise<string | null>;
102
+ selectFile(options?: FileDialogOptions): Promise<SelectedFile | null>; // Native file picker (null if cancelled)
103
+ selectFiles(options?: FileDialogOptions): Promise<SelectedFile[]>; // Multi-file picker
104
+ saveFileAs(content: string, defaultFilename?: string, options?: FileDialogOptions): Promise<string | null>; // Save dialog (returns path or null)
105
+ selectFolder(options?: FileDialogOptions): Promise<string | null>; // Folder picker (null if cancelled)
106
+ read(path: string, options?: { encoding?: 'utf8' | 'base64' | 'binary' }): Promise<string | Uint8Array>; // Read file; utf8 (default)=text, base64=raw b64 string, binary=Uint8Array
107
+ readAsDataUrl(path: string, mimeType?: string): Promise<string>; // Read file as data URL (auto-detects MIME from extension if omitted)
108
+ write(path: string, content: string): Promise<void>; // Write file contents (requires allowFs)
109
+ list(path: string): Promise<FileInfo[]>; // List directory contents (requires allowFs)
110
+ exists(path: string): Promise<boolean>; // Check if file/directory exists (requires allowFs)
111
+ delete(path: string): Promise<void>; // Delete file or directory (requires allowFs)
112
+ };
206
113
 
207
- /**
208
- * Read file contents
209
- * Note: Requires allowFs: true flag when launching msgview/msger
210
- * @param path - File path to read
211
- * @returns File content as string
212
- * @example
213
- * const content = await msgapi.fs.read('/path/to/file.txt');
214
- */
215
- read(path: string): Promise<string>;
114
+ // ── UDP Networking ──────────────────────────────────────────
115
+ // Native UDP — no httpudp WebSocket proxy needed
216
116
 
217
- /**
218
- * Write file contents
219
- * Note: Requires allowFs: true flag when launching msgview/msger
220
- * @param path - File path to write
221
- * @param content - Content to write
222
- * @example
223
- * await msgapi.fs.write('/path/to/file.txt', 'Hello World');
224
- */
225
- write(path: string, content: string): Promise<void>;
117
+ udp?: {
118
+ send(host: string, port: number, data: string | Uint8Array): Promise<void>; // Send UDP packet to host:port
119
+ listen(port: number, callback: (data: Uint8Array, sender: { host: string; port: number }) => void): Promise<{ close: () => void }>; // Listen on port (0=ephemeral), returns {close()}
120
+ sendReceive(host: string, port: number, data: string | Uint8Array, timeoutMs?: number): Promise<Uint8Array>; // Send and wait for response
121
+ broadcast(port: number, data: string | Uint8Array): Promise<void>; // Broadcast to local network
122
+ };
226
123
 
227
- /**
228
- * List directory contents
229
- * Note: Requires allowFs: true flag when launching msgview/msger
230
- * @param path - Directory path to list
231
- * @returns Array of file information
232
- * @example
233
- * const files = await msgapi.fs.list('/path/to/dir');
234
- */
235
- list(path: string): Promise<FileInfo[]>;
124
+ // ── HTTP Fetch (CORS/mixed-content bypass) ──────────────────
125
+ // Routes requests through the native host — no mixed-content or CORS restrictions
126
+ // Returns a real Response object: .ok, .status, .json(), .text(), .headers, .clone()
236
127
 
237
- /**
238
- * Check if file or directory exists
239
- * Note: Requires allowFs: true flag when launching msgview/msger
240
- * @param path - Path to check
241
- * @returns true if exists
242
- * @example
243
- * const exists = await msgapi.fs.exists('/path/to/file.txt');
244
- */
245
- exists(path: string): Promise<boolean>;
128
+ http?: {
129
+ fetch(url: string, init?: RequestInit): Promise<Response>; // Drop-in fetch() replacement via native HTTP
130
+ };
246
131
 
247
- /**
248
- * Delete file or directory
249
- * Note: Requires allowFs: true flag when launching msgview/msger
250
- * @param path - Path to delete
251
- * @example
252
- * await msgapi.fs.delete('/path/to/file.txt');
253
- */
254
- delete(path: string): Promise<void>;
132
+ // ── Shell / Process Execution ─────────────────────────────────
133
+ // Native process execution — run commands, open files with default handler
134
+ // ⚠️ Powerful: grants full process execution. Use only in trusted apps.
135
+
136
+ shell?: {
137
+ exec(command: string, args?: string[], options?: { // Run external command, capture output
138
+ cwd?: string; // Working directory
139
+ timeout?: number; // Timeout in ms (0 = no timeout)
140
+ stdin?: string; // Pipe string to stdin
141
+ encoding?: 'utf8' | 'base64'; // stdout/stderr encoding (default utf8)
142
+ }): Promise<{ stdout: string; stderr: string; exitCode: number }>;
143
+ open(path: string): Promise<void>; // Open file/URL with system default handler (ShellExecute / Intent.ACTION_VIEW)
144
+ showInFolder(path: string): Promise<void>; // Select file in Explorer / file manager
145
+ trash(path: string): Promise<void>; // Move to Recycle Bin / Trash (soft delete, not permanent)
255
146
  };
256
147
  }
257
148
 
258
- /**
259
- * Declare global window.msgapi
260
- */
149
+ // Global window.msgapi declaration
261
150
  declare global {
262
151
  interface Window {
263
152
  msgapi?: MsgAPI;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/msgapidefs",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "description": "TypeScript definitions for msgapi JavaScript API (msgview/msger)",
5
5
  "type": "module",
6
6
  "main": "./msgapidefs.js",
@@ -34,5 +34,8 @@
34
34
  "preversion": "git add -A",
35
35
  "postversion": "git push && git push --tags",
36
36
  "release": "npm run prerelease:local && npm version patch && npm publish --quiet"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^25.2.1"
37
40
  }
38
41
  }