@bobfrankston/msgapidefs 0.1.40 → 0.1.42

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/.commitmsg ADDED
@@ -0,0 +1,9 @@
1
+ Add msgapi.dns.lookup; document that udp host may be a hostname
2
+
3
+ - `dns?: { lookup(hostname, { family?, timeoutMs? }) }` with `MsgDnsAddress` /
4
+ `MsgDnsLookupOptions`. Resolves to [{address, family}] (v4 first, never empty) or
5
+ rejects with error.code 'ENOTFOUND' / 'ETIMEOUT'. msger + msga; msgview on demand.
6
+ - udp.send / udp.sendReceive: `host` documented as IP literal OR hostname, resolved in
7
+ the host by the same resolver. (The hosts always resolved names; it was undocumented.)
8
+ - README: DNS section + platform matrix row; msgapi-plan.md status rows.
9
+ Requirements: msgx/dnsrequest.md (2026-09-17, Claude Code at Bob's direction).
package/README.md CHANGED
@@ -135,8 +135,10 @@ nothing about capability.
135
135
  | `shell.open()` | ❌ | ❌ | ✅ | Android: Intent.ACTION_VIEW; Windows: ShellExecute |
136
136
  | `shell.showInFolder()` | ❌ | ❌ | ❌ | Defined in interface, not yet implemented |
137
137
  | `shell.trash()` | ❌ | ❌ | ❌ | Defined in interface, not yet implemented |
138
+ | **DNS** | | | | **`window.msgapi.dns.*`** (added 2026-09-17) |
139
+ | `dns.lookup()` | ❌ | ✅ | ✅ | Rust `ToSocketAddrs` (getaddrinfo) / C# `System.Net.Dns`; 2 s default timeout, 30 s cache. msger after 0.1.431, msga 1.5.53+. msgview: on demand |
138
140
  | **UDP Networking** | | | | **`window.msgapi.udp.*`** |
139
- | `udp.send()` | ✅ | ✅ | ✅ | Node dgram / Rust UdpSocket / C# UdpClient |
141
+ | `udp.send()` | ✅ | ✅ | ✅ | Node dgram / Rust UdpSocket / C# UdpClient. `host` = IP **or hostname** |
140
142
  | `udp.listen()` | ✅ | ✅ | ✅ | Background receive loop in all hosts |
141
143
  | `udp.sendReceive()` | ✅ | ✅ | ✅ | Send + wait with timeout |
142
144
  | `udp.broadcast()` | ✅ | ✅ | ✅ | 255.255.255.255 broadcast |
@@ -216,7 +218,21 @@ for (const e of exits) {
216
218
 
217
219
  Interpreting common reasons: `LowMemory` = killed by the low-memory killer; `Anr` = main thread hung; `Crash`/`CrashNative` = unhandled exception / native crash; `Freezer` = app was frozen (Android 12+ cached-app freezing — UDP listeners silently suspended, app not actually dead); `UserRequested` = swiped away from recents; `PackageUpdated` = replaced by an APK update; `Signaled` = killed by signal (check `description`).
218
220
 
219
- msga also logs new exit records via MsgALog at startup when `LOGGING=true` in msga.env (`Previous exit: ...`), deduplicated across runs via a `lastSeenExitTimestamp` preference. See [msga/exit-diagnostics.md](../msga/exit-diagnostics.md) for the full design notes.
221
+ msga also logs new exit records via MsgALog at startup when `LOGGING=true` in msga.env (`Previous exit: ...`), deduplicated across runs via a `lastSeenExitTimestamp` preference, plus one `Relaunched <gap> after previous exit: ...` line per start (held until logging is turned on). See [msga/exit-diagnostics.md](../msga/exit-diagnostics.md) for the full design notes.
222
+
223
+ **Staying alive / coming back** (msga Android; other hosts resolve `'n/a'`):
224
+
225
+ - `diag.getInstallInfo()` - Returns `MsgInstallInfo`: `installer` / `selfInstalled` (whether the next update can be silent), `canDrawOverlays`, `canInstallPackages`, `isHomeApp` (1.5.52+), `sdkInt`.
226
+ - `diag.requestHome()` - Make msga the device's **Home app**. Android relaunches the Home app after *any* process death — self-update, System WebView update, crash, low-memory kill — with no other permission. On Android 10+ this shows the system's own one-button "Set as default Home app?" confirmation and resolves `'granted'` / `'denied'`; older Android opens the Home-settings list (`'opened'`). `'already'` if it is Home already. Recommended one-time step for a wall panel; a web app can offer it whenever `isHomeApp` is false. (msga 1.5.52+)
227
+ - `diag.requestOverlay()` - Opens the "Display over other apps" settings page (`'already'` / `'opened'`). The older alternative: with the grant, the post-self-update relaunch is allowed to reach the foreground, but it does nothing for a WebView-update kill. Prefer `requestHome()`.
228
+
229
+ ```javascript
230
+ // Panel setup: one tap, Android's own dialog
231
+ const info = await window.msgapi?.diag?.getInstallInfo?.();
232
+ if (info && info.isHomeApp === false) {
233
+ const r = await window.msgapi.diag.requestHome(); // 'granted' | 'denied' | 'opened' | ...
234
+ }
235
+ ```
220
236
 
221
237
  ### Window Control
222
238
 
@@ -247,10 +263,39 @@ Direct path operations (give the webview full filesystem access — msga only cu
247
263
 
248
264
  > **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.
249
265
 
266
+ ### DNS
267
+
268
+ Page script cannot resolve a hostname — a browser resolves names only inside its own requests and
269
+ never hands the address back. The host can. (Added 2026-09-17 by Claude Code; requirements in
270
+ [../dnsrequest.md](../dnsrequest.md).)
271
+
272
+ - `dns.lookup(hostname, { family?, timeoutMs? })` - Resolves to `[{ address, family }]`, IPv4 first,
273
+ **never empty**; rejects otherwise. `family` (4 | 6) restricts the answer. `timeoutMs` defaults to 2000.
274
+ An IP literal resolves to itself.
275
+ - Rejections carry `error.code`: `'ENOTFOUND'` (no such name, or no address of the requested family)
276
+ or `'ETIMEOUT'` (resolver silent for `timeoutMs`). The message names the hostname and the resolver's reason.
277
+ - Answers are cached in the host for 30 s (the platform resolvers do not expose the record TTL).
278
+ "No such host" is cached the same way (a cold negative lookup was measured at ~2.1 s on Windows — without
279
+ this every command to a vanished name would wait out the timeout before the caller's IP fallback);
280
+ a timeout is not an answer and is never cached. A cached name answers instantly, so `timeoutMs` only bounds an uncached lookup.
281
+ - Absent (`window.msgapi?.dns?.lookup` undefined) in a plain browser, msgview, and older hosts —
282
+ callers fall back to the address they already have.
283
+
284
+ ```ts
285
+ const found = await window.msgapi?.dns?.lookup('wiz_7c47f4.aaz.lt').catch(() => null);
286
+ const ip = found?.[0].address ?? fallbackIp;
287
+ ```
288
+
250
289
  ### UDP Networking
251
290
 
252
291
  Native UDP socket access — no WebSocket proxy needed when running inside a msgapi host.
253
292
 
293
+ `host` in `udp.send` / `udp.sendReceive` is an IP literal **or a hostname**. A hostname is resolved in
294
+ the host process by the same resolver as `dns.lookup` (timeout, cache) before the datagram leaves; one
295
+ that does not resolve rejects with `error.code` `'ENOTFOUND'` / `'ETIMEOUT'`. (Both hosts always
296
+ resolved names here — the platform send calls do it — but it was undocumented and had no timeout,
297
+ cache or typed error before 2026-09-17.) Sockets are IPv4; a name with only IPv6 addresses rejects in msga.
298
+
254
299
  - `udp.send(host, port, data)` - Send UDP packet to specific host
255
300
  - `udp.listen(port, callback)` - Listen for incoming UDP packets (returns `{close()}` handle)
256
301
  - `udp.sendReceive(host, port, data, timeoutMs?)` - Send and wait for response
package/msgapi-plan.md CHANGED
@@ -40,8 +40,10 @@ Shared planning document for msgapi across implementations:
40
40
  | shell.open() | ❌ | ❌ | ✅ | Android: Intent.ACTION_VIEW; Windows: ShellExecute |
41
41
  | shell.showInFolder() | ❌ | ❌ | ❌ | Defined in interface, not yet implemented |
42
42
  | shell.trash() | ❌ | ❌ | ❌ | Defined in interface, not yet implemented |
43
+ | **DNS** (2026-09-17) |
44
+ | dns.lookup() | ❌ | ✅ | ✅ | getaddrinfo via Rust std / C# System.Net.Dns; 2 s timeout, 30 s cache, ENOTFOUND/ETIMEOUT codes. See README. |
43
45
  | **UDP** |
44
- | udp.send() | ✅ | ✅ | ✅ | Node dgram / Rust UdpSocket / C# UdpClient |
46
+ | udp.send() | ✅ | ✅ | ✅ | Node dgram / Rust UdpSocket / C# UdpClient. host = IP or hostname (msger/msga share the dns.lookup resolver) |
45
47
  | udp.listen() | ✅ | ✅ | ✅ | Background receive loop in all hosts |
46
48
  | udp.sendReceive() | ✅ | ✅ | ✅ | Send + wait with timeout |
47
49
  | udp.broadcast() | ✅ | ✅ | ✅ | 255.255.255.255 broadcast |
@@ -0,0 +1,194 @@
1
+ export interface MsgResult {
2
+ button: string;
3
+ value?: string;
4
+ form?: Record<string, any>;
5
+ closed?: boolean;
6
+ dismissed?: boolean;
7
+ timeout?: boolean;
8
+ }
9
+ export interface FileInfo {
10
+ name: string;
11
+ path: string;
12
+ isDir: boolean;
13
+ size: number;
14
+ modified?: string;
15
+ created?: string;
16
+ attributes?: number;
17
+ }
18
+ export interface FileDialogOptions {
19
+ title?: string;
20
+ defaultFilename?: string;
21
+ filters?: Array<{
22
+ name: string;
23
+ extensions: string[];
24
+ }>;
25
+ defaultPath?: string;
26
+ }
27
+ export interface SelectedFile {
28
+ name: string;
29
+ path: string;
30
+ content: string;
31
+ }
32
+ export type MsgHost = 'msga' | 'msger' | 'msgview';
33
+ export type MsgPlatform = 'android' | 'windows' | 'linux' | 'macos';
34
+ export type AutoUpdateMode = 'auto' | 'check' | 'off';
35
+ export type LoggingMode = 'msga' | 'msgapi' | 'on' | 'off' | 'none';
36
+ export interface MsgHostInfo {
37
+ host: MsgHost;
38
+ version: string;
39
+ platform: MsgPlatform;
40
+ appName?: string;
41
+ updateUrl?: string;
42
+ apkUrl?: string;
43
+ localIp?: string;
44
+ localMask?: string;
45
+ }
46
+ export interface MsgApkInfo {
47
+ name: string;
48
+ url: string;
49
+ description: string;
50
+ icon: string;
51
+ file: string;
52
+ sizeMB: number;
53
+ built: string;
54
+ default?: boolean;
55
+ }
56
+ export interface MsgExitInfo {
57
+ timestamp: number;
58
+ reason: string;
59
+ description?: string;
60
+ importance?: string;
61
+ pss?: number;
62
+ rss?: number;
63
+ processName?: string;
64
+ pid?: number;
65
+ status?: number;
66
+ stateSummary?: string;
67
+ trace?: string;
68
+ }
69
+ export interface MsgVersionInfo {
70
+ version: string;
71
+ buildDate: string;
72
+ apks: MsgApkInfo[];
73
+ }
74
+ /** Why an update prompted, and whether the post-update relaunch can reach the foreground.
75
+ * Android permits a silent self-update only when the updating app is the installer of record, so
76
+ * a browser or adb sideload makes the NEXT update prompt (selfInstalled false). Separately, a
77
+ * background process may not start an activity without canDrawOverlays, which is why a silent
78
+ * update can leave the user on the home screen. */
79
+ export interface MsgInstallInfo {
80
+ package: string;
81
+ installer: string;
82
+ selfInstalled: boolean;
83
+ canDrawOverlays: boolean;
84
+ canInstallPackages: boolean;
85
+ isHomeApp?: boolean;
86
+ sdkInt: number;
87
+ }
88
+ /** Handle returned by udp.listen(). send/broadcast leave from this listener's own socket, so the
89
+ * device's reply arrives back at this listener rather than at whichever one registered first.
90
+ * send/broadcast are optional: msga builds before 1.5.45 return a handle with only close(). */
91
+ export interface UdpListenHandle {
92
+ close(): void;
93
+ send?(host: string, port: number, data: string | Uint8Array): Promise<void>;
94
+ broadcast?(port: number, data: string | Uint8Array): Promise<void>;
95
+ }
96
+ /** One address from dns.lookup(). */
97
+ export interface MsgDnsAddress {
98
+ address: string;
99
+ family: 4 | 6;
100
+ }
101
+ /** Options for dns.lookup(). */
102
+ export interface MsgDnsLookupOptions {
103
+ family?: 4 | 6;
104
+ timeoutMs?: number;
105
+ }
106
+ export interface MsgAPI {
107
+ version?: string;
108
+ updateUrl?: string;
109
+ info?: MsgHostInfo;
110
+ setLogging?(value?: LoggingMode, name?: string): LoggingMode;
111
+ setAutoUpdate?(value?: AutoUpdateMode): AutoUpdateMode;
112
+ installUpdate?(url: string): Promise<void>;
113
+ getNetworkInfo?(): Promise<{
114
+ localIp: string;
115
+ localMask: string;
116
+ }>;
117
+ diag?: {
118
+ getExitReasons(max?: number): Promise<MsgExitInfo[]>;
119
+ getInstallInfo?(): Promise<MsgInstallInfo>;
120
+ requestOverlay?(): Promise<'already' | 'opened' | 'n/a'>;
121
+ requestHome?(): Promise<'already' | 'granted' | 'denied' | 'opened' | 'n/a'>;
122
+ };
123
+ toggleFullscreen(): void;
124
+ setFullscreen(enabled: boolean): void;
125
+ minimize(): void;
126
+ maximize(): void;
127
+ setSize(width: number, height: number): void;
128
+ setPosition(x: number, y: number): void;
129
+ setAlwaysOnTop(enabled: boolean): void;
130
+ close(result?: Partial<MsgResult>): void;
131
+ fs?: {
132
+ selectFile(options?: FileDialogOptions): Promise<SelectedFile | null>;
133
+ selectFiles(options?: FileDialogOptions): Promise<SelectedFile[]>;
134
+ saveFileAs(content: string, defaultFilename?: string, options?: FileDialogOptions): Promise<string | null>;
135
+ selectFolder(options?: FileDialogOptions): Promise<string | null>;
136
+ read(path: string, options?: {
137
+ encoding?: 'utf8' | 'base64' | 'binary';
138
+ }): Promise<string | Uint8Array>;
139
+ readAsDataUrl(path: string, mimeType?: string): Promise<string>;
140
+ write(path: string, content: string): Promise<void>;
141
+ list(path: string): Promise<FileInfo[]>;
142
+ exists(path: string): Promise<boolean>;
143
+ delete(path: string): Promise<void>;
144
+ };
145
+ dns?: {
146
+ /** Resolve a hostname in the host process. Resolves to at least one address (v4 first), or
147
+ * REJECTS — never an empty success. The Error's `code` is 'ENOTFOUND' (no such name / no
148
+ * address of the requested family) or 'ETIMEOUT' (resolver did not answer within timeoutMs);
149
+ * the message names the hostname and the resolver's reason. An IP literal resolves to itself.
150
+ * Answers are cached in the host for ~30 s. msger after 0.1.431, msga 1.5.53+. */
151
+ lookup(hostname: string, opts?: MsgDnsLookupOptions): Promise<MsgDnsAddress[]>;
152
+ };
153
+ udp?: {
154
+ send(host: string, port: number, data: string | Uint8Array): Promise<void>;
155
+ listen(port: number, callback: (data: Uint8Array, sender: {
156
+ host: string;
157
+ port: number;
158
+ }) => void): Promise<UdpListenHandle>;
159
+ sendReceive(host: string, port: number, data: string | Uint8Array, timeoutMs?: number): Promise<Uint8Array>;
160
+ broadcast(port: number, data: string | Uint8Array): Promise<void>;
161
+ };
162
+ tcp?: {
163
+ connect(host: string, port: number, tls: boolean): Promise<number>;
164
+ write(streamId: number, data: string): Promise<void>;
165
+ onData(streamId: number, callback: (data: string) => void): void;
166
+ onClose(streamId: number, callback: (hadError: boolean) => void): void;
167
+ onError(streamId: number, callback: (message: string) => void): void;
168
+ upgradeTLS(streamId: number, servername: string): Promise<void>;
169
+ close(streamId: number): void;
170
+ };
171
+ http?: {
172
+ fetch(url: string, init?: RequestInit): Promise<Response>;
173
+ };
174
+ shell?: {
175
+ exec(command: string, args?: string[], options?: {
176
+ cwd?: string;
177
+ timeout?: number;
178
+ stdin?: string;
179
+ encoding?: 'utf8' | 'base64';
180
+ }): Promise<{
181
+ stdout: string;
182
+ stderr: string;
183
+ exitCode: number;
184
+ }>;
185
+ open(path: string): Promise<void>;
186
+ showInFolder(path: string): Promise<void>;
187
+ trash(path: string): Promise<void>;
188
+ };
189
+ }
190
+ declare global {
191
+ interface Window {
192
+ msgapi?: MsgAPI;
193
+ }
194
+ }
package/msgapidefs.js ADDED
@@ -0,0 +1,8 @@
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
+ export {};
package/msgapidefs.ts CHANGED
@@ -101,6 +101,7 @@ export interface MsgInstallInfo {
101
101
  selfInstalled: boolean; // true => next update can be silent
102
102
  canDrawOverlays: boolean; // true => post-update relaunch is allowed to foreground
103
103
  canInstallPackages: boolean;// "Install unknown apps" for this app
104
+ isHomeApp?: boolean; // true => msga is the device's Home app: Android relaunches it after ANY process death (msga 1.5.52+)
104
105
  sdkInt: number;
105
106
  }
106
107
 
@@ -113,6 +114,18 @@ export interface UdpListenHandle {
113
114
  broadcast?(port: number, data: string | Uint8Array): Promise<void>;
114
115
  }
115
116
 
117
+ /** One address from dns.lookup(). */
118
+ export interface MsgDnsAddress {
119
+ address: string; // Literal address, e.g. "172.20.1.42" or "fe80::1"
120
+ family: 4 | 6;
121
+ }
122
+
123
+ /** Options for dns.lookup(). */
124
+ export interface MsgDnsLookupOptions {
125
+ family?: 4 | 6; // Restrict the answer to one family; default = both, v4 first
126
+ timeoutMs?: number; // Bounded wait for the resolver (default 2000)
127
+ }
128
+
116
129
  export interface MsgAPI {
117
130
 
118
131
  version?: string; // Host app version (e.g. "1.3.0") — semver string, compare numerically not lexicographically
@@ -134,6 +147,7 @@ export interface MsgAPI {
134
147
  getExitReasons(max?: number): Promise<MsgExitInfo[]>; // Previous-run exit records, newest first (default max 5). msga Android 11+ only; [] elsewhere.
135
148
  getInstallInfo?(): Promise<MsgInstallInfo>; // Installer of record + permission state (msga 1.5.48+)
136
149
  requestOverlay?(): Promise<'already' | 'opened' | 'n/a'>; // Open the Display-over-other-apps settings page
150
+ requestHome?(): Promise<'already' | 'granted' | 'denied' | 'opened' | 'n/a'>; // Make msga the Home app: system one-button confirmation (Android 10+), settings list before. Home app = automatic relaunch after self-update / WebView update / crash. msga 1.5.52+
137
151
  };
138
152
 
139
153
  // ── Window Control ──────────────────────────────────────────
@@ -162,8 +176,25 @@ export interface MsgAPI {
162
176
  delete(path: string): Promise<void>; // Delete file or directory (requires allowFs)
163
177
  };
164
178
 
179
+ // ── DNS ─────────────────────────────────────────────────────
180
+ // 2026-09-17 — Claude Code (Fable 5.1), at Bob's direction (dnsrequest.md). Page script cannot
181
+ // resolve a name itself; the host can. Absent (plain browser, older host) = "not available":
182
+ // callers fall back to the address they already have.
183
+
184
+ dns?: {
185
+ /** Resolve a hostname in the host process. Resolves to at least one address (v4 first), or
186
+ * REJECTS — never an empty success. The Error's `code` is 'ENOTFOUND' (no such name / no
187
+ * address of the requested family) or 'ETIMEOUT' (resolver did not answer within timeoutMs);
188
+ * the message names the hostname and the resolver's reason. An IP literal resolves to itself.
189
+ * Answers are cached in the host for ~30 s. msger after 0.1.431, msga 1.5.53+. */
190
+ lookup(hostname: string, opts?: MsgDnsLookupOptions): Promise<MsgDnsAddress[]>;
191
+ };
192
+
165
193
  // ── UDP Networking ──────────────────────────────────────────
166
194
  // Native UDP — no httpudp WebSocket proxy needed
195
+ // `host` is an IP literal OR a hostname. A hostname is resolved in the host process (same
196
+ // resolver, timeout and cache as dns.lookup) before the datagram leaves; one that does not
197
+ // resolve rejects with code 'ENOTFOUND' / 'ETIMEOUT' so the caller can fall back to an address.
167
198
 
168
199
  udp?: {
169
200
  send(host: string, port: number, data: string | Uint8Array): Promise<void>; // Send UDP packet to host:port
package/npmchanges.md ADDED
@@ -0,0 +1,6 @@
1
+ # npm Publish Changes
2
+
3
+ ## v0.1.41 — 2026-09-14
4
+
5
+ diag.requestHome() and MsgInstallInfo.isHomeApp (msga 1.5.52): make msga the device's Home app via Android's own confirmation dialog so relaunch after self-update / WebView update / crash is automatic; README Diagnostics documents getInstallInfo/requestHome/requestOverlay and the per-start "Relaunched <gap>" log line.
6
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/msgapidefs",
3
- "version": "0.1.40",
3
+ "version": "0.1.42",
4
4
  "description": "TypeScript definitions for msgapi JavaScript API (msgview/msger)",
5
5
  "type": "module",
6
6
  "main": "./msgapidefs.js",
package/tsconfig.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "compilerOptions": {
3
3
  "types": ["node"],
4
4
  "target": "ES2020",
5
- "module": "ES2020",
5
+ "module": "NodeNext",
6
6
  "lib": ["ES2020", "DOM"],
7
7
  "declaration": true,
8
8
  "outDir": ".",
@@ -11,7 +11,7 @@
11
11
  "esModuleInterop": true,
12
12
  "skipLibCheck": true,
13
13
  "forceConsistentCasingInFileNames": true,
14
- "moduleResolution": "node",
14
+ "moduleResolution": "NodeNext",
15
15
  "newLine": "lf"
16
16
  },
17
17
  "include": ["msgapidefs.ts"],