@bobfrankston/msgapidefs 0.1.41 → 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 +9 -1
- package/README.md +32 -1
- package/msgapi-plan.md +3 -1
- package/msgapidefs.d.ts +194 -0
- package/msgapidefs.js +8 -0
- package/msgapidefs.ts +29 -0
- package/npmchanges.md +6 -0
- package/package.json +1 -1
package/.commitmsg
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
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 |
|
|
@@ -261,10 +263,39 @@ Direct path operations (give the webview full filesystem access — msga only cu
|
|
|
261
263
|
|
|
262
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.
|
|
263
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
|
+
|
|
264
289
|
### UDP Networking
|
|
265
290
|
|
|
266
291
|
Native UDP socket access — no WebSocket proxy needed when running inside a msgapi host.
|
|
267
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
|
+
|
|
268
299
|
- `udp.send(host, port, data)` - Send UDP packet to specific host
|
|
269
300
|
- `udp.listen(port, callback)` - Listen for incoming UDP packets (returns `{close()}` handle)
|
|
270
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 |
|
package/msgapidefs.d.ts
ADDED
|
@@ -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
|
@@ -114,6 +114,18 @@ export interface UdpListenHandle {
|
|
|
114
114
|
broadcast?(port: number, data: string | Uint8Array): Promise<void>;
|
|
115
115
|
}
|
|
116
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
|
+
|
|
117
129
|
export interface MsgAPI {
|
|
118
130
|
|
|
119
131
|
version?: string; // Host app version (e.g. "1.3.0") — semver string, compare numerically not lexicographically
|
|
@@ -164,8 +176,25 @@ export interface MsgAPI {
|
|
|
164
176
|
delete(path: string): Promise<void>; // Delete file or directory (requires allowFs)
|
|
165
177
|
};
|
|
166
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
|
+
|
|
167
193
|
// ── UDP Networking ──────────────────────────────────────────
|
|
168
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.
|
|
169
198
|
|
|
170
199
|
udp?: {
|
|
171
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
|
+
|