@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 +196 -6
- package/msgapi-plan.md +165 -67
- package/msgapidefs.ts +122 -233
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
# @bobfrankston/msgapidefs
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
- `fs.
|
|
59
|
-
- `fs.
|
|
60
|
-
- `fs.
|
|
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 (
|
|
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() |
|
|
17
|
-
| setFullscreen(bool) | ✅ | ✅ |
|
|
18
|
-
| minimize() | ✅ | ✅ |
|
|
19
|
-
| maximize() | ✅ | ✅ |
|
|
20
|
-
| setSize(w,h) | ✅ | ✅ |
|
|
21
|
-
| setPosition(x,y) | ✅ | ✅ |
|
|
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?) | ✅ | ✅ |
|
|
24
|
+
| close(result?) | ✅ | ✅ | ✅ | |
|
|
25
|
+
| navigate(url) | ❌ | ❌ | ✅ | msga only |
|
|
26
|
+
| reload() | ❌ | ❌ | ✅ | msga only |
|
|
24
27
|
| **File System** |
|
|
25
|
-
| fs.selectFile() |
|
|
26
|
-
| fs.selectFiles() |
|
|
27
|
-
| fs.saveFileAs() |
|
|
28
|
-
| fs.selectFolder() |
|
|
29
|
-
| fs.read(path) |
|
|
30
|
-
| fs.
|
|
31
|
-
| fs.
|
|
32
|
-
| fs.
|
|
33
|
-
| fs.
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
82
|
+
### msga Implementation Status
|
|
38
83
|
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
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
|
-
##
|
|
47
|
-
|
|
48
|
-
### 1. UDP Communication
|
|
93
|
+
## UDP Interface
|
|
49
94
|
|
|
50
|
-
|
|
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
|
-
**
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
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
|
-
**
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
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
|
-
|
|
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:
|
|
164
|
-
1.
|
|
165
|
-
2.
|
|
166
|
-
3.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
2
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
### Phase 3:
|
|
175
|
-
1.
|
|
176
|
-
2.
|
|
177
|
-
3.
|
|
178
|
-
|
|
179
|
-
|
|
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.
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
export interface
|
|
76
|
-
//
|
|
77
|
-
//
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
229
|
-
|
|
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
|
-
|
|
239
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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.
|
|
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
|
}
|