uxnan-bridge 0.0.1-alpha.20260621
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 +150 -0
- package/dist/src/account-status.d.ts +13 -0
- package/dist/src/account-status.js +78 -0
- package/dist/src/account-status.js.map +1 -0
- package/dist/src/adapters/base-adapter.d.ts +18 -0
- package/dist/src/adapters/base-adapter.js +15 -0
- package/dist/src/adapters/base-adapter.js.map +1 -0
- package/dist/src/adapters/claude-adapter.d.ts +102 -0
- package/dist/src/adapters/claude-adapter.js +486 -0
- package/dist/src/adapters/claude-adapter.js.map +1 -0
- package/dist/src/adapters/claude-tools.d.ts +25 -0
- package/dist/src/adapters/claude-tools.js +146 -0
- package/dist/src/adapters/claude-tools.js.map +1 -0
- package/dist/src/adapters/codex-adapter.d.ts +116 -0
- package/dist/src/adapters/codex-adapter.js +912 -0
- package/dist/src/adapters/codex-adapter.js.map +1 -0
- package/dist/src/adapters/codex-app-server.d.ts +74 -0
- package/dist/src/adapters/codex-app-server.js +225 -0
- package/dist/src/adapters/codex-app-server.js.map +1 -0
- package/dist/src/adapters/codex-approval.d.ts +88 -0
- package/dist/src/adapters/codex-approval.js +160 -0
- package/dist/src/adapters/codex-approval.js.map +1 -0
- package/dist/src/adapters/codex-tools.d.ts +18 -0
- package/dist/src/adapters/codex-tools.js +106 -0
- package/dist/src/adapters/codex-tools.js.map +1 -0
- package/dist/src/adapters/content-blocks.d.ts +68 -0
- package/dist/src/adapters/content-blocks.js +205 -0
- package/dist/src/adapters/content-blocks.js.map +1 -0
- package/dist/src/adapters/echo-agent-adapter.d.ts +23 -0
- package/dist/src/adapters/echo-agent-adapter.js +72 -0
- package/dist/src/adapters/echo-agent-adapter.js.map +1 -0
- package/dist/src/adapters/gemini-adapter.d.ts +87 -0
- package/dist/src/adapters/gemini-adapter.js +594 -0
- package/dist/src/adapters/gemini-adapter.js.map +1 -0
- package/dist/src/adapters/gemini-tools.d.ts +4 -0
- package/dist/src/adapters/gemini-tools.js +48 -0
- package/dist/src/adapters/gemini-tools.js.map +1 -0
- package/dist/src/adapters/opencode-adapter.d.ts +74 -0
- package/dist/src/adapters/opencode-adapter.js +418 -0
- package/dist/src/adapters/opencode-adapter.js.map +1 -0
- package/dist/src/adapters/opencode-tools.d.ts +2 -0
- package/dist/src/adapters/opencode-tools.js +41 -0
- package/dist/src/adapters/opencode-tools.js.map +1 -0
- package/dist/src/adapters/pi-adapter.d.ts +92 -0
- package/dist/src/adapters/pi-adapter.js +467 -0
- package/dist/src/adapters/pi-adapter.js.map +1 -0
- package/dist/src/adapters/pi-tools.d.ts +10 -0
- package/dist/src/adapters/pi-tools.js +72 -0
- package/dist/src/adapters/pi-tools.js.map +1 -0
- package/dist/src/adapters/process-agent-adapter.d.ts +24 -0
- package/dist/src/adapters/process-agent-adapter.js +111 -0
- package/dist/src/adapters/process-agent-adapter.js.map +1 -0
- package/dist/src/adapters/resolve-claude.d.ts +13 -0
- package/dist/src/adapters/resolve-claude.js +57 -0
- package/dist/src/adapters/resolve-claude.js.map +1 -0
- package/dist/src/adapters/resolve-codex.d.ts +13 -0
- package/dist/src/adapters/resolve-codex.js +48 -0
- package/dist/src/adapters/resolve-codex.js.map +1 -0
- package/dist/src/adapters/resolve-gemini.d.ts +13 -0
- package/dist/src/adapters/resolve-gemini.js +47 -0
- package/dist/src/adapters/resolve-gemini.js.map +1 -0
- package/dist/src/adapters/resolve-opencode.d.ts +11 -0
- package/dist/src/adapters/resolve-opencode.js +49 -0
- package/dist/src/adapters/resolve-opencode.js.map +1 -0
- package/dist/src/adapters/resolve-pi.d.ts +13 -0
- package/dist/src/adapters/resolve-pi.js +46 -0
- package/dist/src/adapters/resolve-pi.js.map +1 -0
- package/dist/src/adapters/run-options.d.ts +22 -0
- package/dist/src/adapters/run-options.js +48 -0
- package/dist/src/adapters/run-options.js.map +1 -0
- package/dist/src/adapters/spawn.d.ts +20 -0
- package/dist/src/adapters/spawn.js +16 -0
- package/dist/src/adapters/spawn.js.map +1 -0
- package/dist/src/agents/agent-manager.d.ts +98 -0
- package/dist/src/agents/agent-manager.js +433 -0
- package/dist/src/agents/agent-manager.js.map +1 -0
- package/dist/src/agents/attachments.d.ts +28 -0
- package/dist/src/agents/attachments.js +121 -0
- package/dist/src/agents/attachments.js.map +1 -0
- package/dist/src/bridge-context.d.ts +45 -0
- package/dist/src/bridge-context.js +2 -0
- package/dist/src/bridge-context.js.map +1 -0
- package/dist/src/bridge-status.d.ts +12 -0
- package/dist/src/bridge-status.js +17 -0
- package/dist/src/bridge-status.js.map +1 -0
- package/dist/src/bridge.d.ts +37 -0
- package/dist/src/bridge.js +446 -0
- package/dist/src/bridge.js.map +1 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +194 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/conversation/session-history.d.ts +27 -0
- package/dist/src/conversation/session-history.js +1082 -0
- package/dist/src/conversation/session-history.js.map +1 -0
- package/dist/src/conversation/thread-store.d.ts +74 -0
- package/dist/src/conversation/thread-store.js +366 -0
- package/dist/src/conversation/thread-store.js.map +1 -0
- package/dist/src/daemon-config.d.ts +123 -0
- package/dist/src/daemon-config.js +64 -0
- package/dist/src/daemon-config.js.map +1 -0
- package/dist/src/daemon-state.d.ts +27 -0
- package/dist/src/daemon-state.js +76 -0
- package/dist/src/daemon-state.js.map +1 -0
- package/dist/src/git/git-runner.d.ts +24 -0
- package/dist/src/git/git-runner.js +63 -0
- package/dist/src/git/git-runner.js.map +1 -0
- package/dist/src/git/git-service.d.ts +76 -0
- package/dist/src/git/git-service.js +435 -0
- package/dist/src/git/git-service.js.map +1 -0
- package/dist/src/handler-router.d.ts +34 -0
- package/dist/src/handler-router.js +67 -0
- package/dist/src/handler-router.js.map +1 -0
- package/dist/src/handlers/account-handler.d.ts +4 -0
- package/dist/src/handlers/account-handler.js +27 -0
- package/dist/src/handlers/account-handler.js.map +1 -0
- package/dist/src/handlers/agent-handler.d.ts +2 -0
- package/dist/src/handlers/agent-handler.js +8 -0
- package/dist/src/handlers/agent-handler.js.map +1 -0
- package/dist/src/handlers/bridge-control-handler.d.ts +2 -0
- package/dist/src/handlers/bridge-control-handler.js +64 -0
- package/dist/src/handlers/bridge-control-handler.js.map +1 -0
- package/dist/src/handlers/desktop-handler.d.ts +12 -0
- package/dist/src/handlers/desktop-handler.js +5 -0
- package/dist/src/handlers/desktop-handler.js.map +1 -0
- package/dist/src/handlers/git-handler.d.ts +2 -0
- package/dist/src/handlers/git-handler.js +82 -0
- package/dist/src/handlers/git-handler.js.map +1 -0
- package/dist/src/handlers/index.d.ts +8 -0
- package/dist/src/handlers/index.js +22 -0
- package/dist/src/handlers/index.js.map +1 -0
- package/dist/src/handlers/not-implemented.d.ts +10 -0
- package/dist/src/handlers/not-implemented.js +21 -0
- package/dist/src/handlers/not-implemented.js.map +1 -0
- package/dist/src/handlers/notifications-handler.d.ts +2 -0
- package/dist/src/handlers/notifications-handler.js +62 -0
- package/dist/src/handlers/notifications-handler.js.map +1 -0
- package/dist/src/handlers/params.d.ts +11 -0
- package/dist/src/handlers/params.js +72 -0
- package/dist/src/handlers/params.js.map +1 -0
- package/dist/src/handlers/project-handler.d.ts +2 -0
- package/dist/src/handlers/project-handler.js +6 -0
- package/dist/src/handlers/project-handler.js.map +1 -0
- package/dist/src/handlers/thread-context-handler.d.ts +2 -0
- package/dist/src/handlers/thread-context-handler.js +211 -0
- package/dist/src/handlers/thread-context-handler.js.map +1 -0
- package/dist/src/handlers/workspace-handler.d.ts +2 -0
- package/dist/src/handlers/workspace-handler.js +101 -0
- package/dist/src/handlers/workspace-handler.js.map +1 -0
- package/dist/src/hooks/claude-approval-hook.d.ts +7 -0
- package/dist/src/hooks/claude-approval-hook.js +95 -0
- package/dist/src/hooks/claude-approval-hook.js.map +1 -0
- package/dist/src/hooks/gemini-approval-hook.d.ts +7 -0
- package/dist/src/hooks/gemini-approval-hook.js +113 -0
- package/dist/src/hooks/gemini-approval-hook.js.map +1 -0
- package/dist/src/index.d.ts +62 -0
- package/dist/src/index.js +65 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/keyring-secret-store.d.ts +36 -0
- package/dist/src/keyring-secret-store.js +70 -0
- package/dist/src/keyring-secret-store.js.map +1 -0
- package/dist/src/lock-file.d.ts +18 -0
- package/dist/src/lock-file.js +60 -0
- package/dist/src/lock-file.js.map +1 -0
- package/dist/src/logger.d.ts +28 -0
- package/dist/src/logger.js +99 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/pairing/pairing-code-service.d.ts +45 -0
- package/dist/src/pairing/pairing-code-service.js +183 -0
- package/dist/src/pairing/pairing-code-service.js.map +1 -0
- package/dist/src/projects/project-registry.d.ts +14 -0
- package/dist/src/projects/project-registry.js +60 -0
- package/dist/src/projects/project-registry.js.map +1 -0
- package/dist/src/push/push-sender.d.ts +21 -0
- package/dist/src/push/push-sender.js +96 -0
- package/dist/src/push/push-sender.js.map +1 -0
- package/dist/src/push/push-service.d.ts +122 -0
- package/dist/src/push/push-service.js +260 -0
- package/dist/src/push/push-service.js.map +1 -0
- package/dist/src/qr.d.ts +17 -0
- package/dist/src/qr.js +31 -0
- package/dist/src/qr.js.map +1 -0
- package/dist/src/secret-store.d.ts +23 -0
- package/dist/src/secret-store.js +27 -0
- package/dist/src/secret-store.js.map +1 -0
- package/dist/src/secure-device-state.d.ts +16 -0
- package/dist/src/secure-device-state.js +63 -0
- package/dist/src/secure-device-state.js.map +1 -0
- package/dist/src/service-installer.d.ts +57 -0
- package/dist/src/service-installer.js +254 -0
- package/dist/src/service-installer.js.map +1 -0
- package/dist/src/session-state.d.ts +14 -0
- package/dist/src/session-state.js +19 -0
- package/dist/src/session-state.js.map +1 -0
- package/dist/src/transport/crypto.d.ts +43 -0
- package/dist/src/transport/crypto.js +78 -0
- package/dist/src/transport/crypto.js.map +1 -0
- package/dist/src/transport/lan-server.d.ts +33 -0
- package/dist/src/transport/lan-server.js +105 -0
- package/dist/src/transport/lan-server.js.map +1 -0
- package/dist/src/transport/local-hosts.d.ts +17 -0
- package/dist/src/transport/local-hosts.js +30 -0
- package/dist/src/transport/local-hosts.js.map +1 -0
- package/dist/src/transport/mdns-advertiser.d.ts +83 -0
- package/dist/src/transport/mdns-advertiser.js +282 -0
- package/dist/src/transport/mdns-advertiser.js.map +1 -0
- package/dist/src/transport/message-io.d.ts +33 -0
- package/dist/src/transport/message-io.js +87 -0
- package/dist/src/transport/message-io.js.map +1 -0
- package/dist/src/transport/outbound-log.d.ts +24 -0
- package/dist/src/transport/outbound-log.js +78 -0
- package/dist/src/transport/outbound-log.js.map +1 -0
- package/dist/src/transport/relay-client.d.ts +19 -0
- package/dist/src/transport/relay-client.js +27 -0
- package/dist/src/transport/relay-client.js.map +1 -0
- package/dist/src/transport/secure-channel.d.ts +33 -0
- package/dist/src/transport/secure-channel.js +81 -0
- package/dist/src/transport/secure-channel.js.map +1 -0
- package/dist/src/transport/server-handshake.d.ts +49 -0
- package/dist/src/transport/server-handshake.js +137 -0
- package/dist/src/transport/server-handshake.js.map +1 -0
- package/dist/src/transport/session-handler.d.ts +19 -0
- package/dist/src/transport/session-handler.js +134 -0
- package/dist/src/transport/session-handler.js.map +1 -0
- package/dist/src/transport/session-registry.d.ts +58 -0
- package/dist/src/transport/session-registry.js +91 -0
- package/dist/src/transport/session-registry.js.map +1 -0
- package/dist/src/transport/trust-store.d.ts +23 -0
- package/dist/src/transport/trust-store.js +33 -0
- package/dist/src/transport/trust-store.js.map +1 -0
- package/dist/src/transport/ws-adapter.d.ts +7 -0
- package/dist/src/transport/ws-adapter.js +16 -0
- package/dist/src/transport/ws-adapter.js.map +1 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +13 -0
- package/dist/src/version.js.map +1 -0
- package/dist/src/workspace/browse-service.d.ts +10 -0
- package/dist/src/workspace/browse-service.js +97 -0
- package/dist/src/workspace/browse-service.js.map +1 -0
- package/dist/src/workspace/checkpoint-service.d.ts +21 -0
- package/dist/src/workspace/checkpoint-service.js +219 -0
- package/dist/src/workspace/checkpoint-service.js.map +1 -0
- package/dist/src/workspace/path-guard.d.ts +7 -0
- package/dist/src/workspace/path-guard.js +51 -0
- package/dist/src/workspace/path-guard.js.map +1 -0
- package/dist/src/workspace/workspace-service.d.ts +8 -0
- package/dist/src/workspace/workspace-service.js +111 -0
- package/dist/src/workspace/workspace-service.js.map +1 -0
- package/package.json +46 -0
- package/scripts/extract-gemini-hook.mjs +16 -0
- package/scripts/fake-approval-bridge.mjs +23 -0
- package/scripts/install-service-linux.sh +38 -0
- package/scripts/install-service-macos.sh +38 -0
- package/scripts/install-service-windows.ps1 +26 -0
- package/scripts/test-gemini-hook-e2e.mjs +168 -0
- package/scripts/write-gemini-settings.mjs +31 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { MessageIO } from './message-io.js';
|
|
2
|
+
/** Result of a `/pair/resolve` lookup: an HTTP status + JSON body to return. */
|
|
3
|
+
export interface PairResolveResult {
|
|
4
|
+
status: number;
|
|
5
|
+
json: unknown;
|
|
6
|
+
}
|
|
7
|
+
/** Result of an agent-hook approval call: an HTTP status + JSON body. */
|
|
8
|
+
export interface HookApprovalResult {
|
|
9
|
+
status: number;
|
|
10
|
+
json: unknown;
|
|
11
|
+
}
|
|
12
|
+
export interface LanServerOptions {
|
|
13
|
+
port: number;
|
|
14
|
+
host?: string;
|
|
15
|
+
onConnection: (io: MessageIO) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Optional handler for `GET /pair/resolve?code=…` (manual-code pairing). Given
|
|
18
|
+
* the submitted code and the client IP, returns the HTTP status + JSON body.
|
|
19
|
+
* Omitted → the route 404s.
|
|
20
|
+
*/
|
|
21
|
+
onPairResolve?: (code: string, ip: string) => PairResolveResult;
|
|
22
|
+
/**
|
|
23
|
+
* Optional handler for `POST /agent-hook/approval` (the Claude `PreToolUse`
|
|
24
|
+
* hook round-trip). Given the parsed JSON body and the `x-uxnan-hook-token`
|
|
25
|
+
* header, resolves once the user answers on the phone. Omitted → 404.
|
|
26
|
+
*/
|
|
27
|
+
onHookApproval?: (body: unknown, token: string | undefined) => Promise<HookApprovalResult>;
|
|
28
|
+
}
|
|
29
|
+
export interface LanServerHandle {
|
|
30
|
+
port: number;
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
export declare function startLanServer(options: LanServerOptions): Promise<LanServerHandle>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct-LAN server. The phone may connect here without the relay
|
|
3
|
+
* (architecture/02a §5.9.3); the E2EE semantics are identical.
|
|
4
|
+
*
|
|
5
|
+
* It is an `http.Server` with a WebSocket server attached: WS upgrades carry the
|
|
6
|
+
* E2EE session (the primary path), and a single plain-HTTP route —
|
|
7
|
+
* `GET /pair/resolve?code=<code>` — backs manual-code pairing (the phone trades a
|
|
8
|
+
* code shown on the PC for the pairing payload; see `pairing/pairing-code-service.ts`).
|
|
9
|
+
*/
|
|
10
|
+
import { createServer } from 'node:http';
|
|
11
|
+
import { WebSocketServer } from 'ws';
|
|
12
|
+
import { wsToMessageIO } from './ws-adapter.js';
|
|
13
|
+
export function startLanServer(options) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const httpServer = createServer((req, res) => handleHttp(req, res, options));
|
|
16
|
+
const wss = new WebSocketServer({ server: httpServer });
|
|
17
|
+
wss.on('connection', (ws) => options.onConnection(wsToMessageIO(ws)));
|
|
18
|
+
const onError = (err) => reject(err);
|
|
19
|
+
httpServer.once('error', onError);
|
|
20
|
+
httpServer.listen(options.port, options.host, () => {
|
|
21
|
+
httpServer.removeListener('error', onError);
|
|
22
|
+
const address = httpServer.address();
|
|
23
|
+
const port = typeof address === 'object' && address !== null ? address.port : options.port;
|
|
24
|
+
resolve({
|
|
25
|
+
port,
|
|
26
|
+
close: () => new Promise((res) => {
|
|
27
|
+
wss.close(() => httpServer.close(() => res()));
|
|
28
|
+
}),
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function handleHttp(req, res, options) {
|
|
34
|
+
const send = (status, json) => {
|
|
35
|
+
res.writeHead(status, { 'content-type': 'application/json' });
|
|
36
|
+
res.end(JSON.stringify(json));
|
|
37
|
+
};
|
|
38
|
+
let url;
|
|
39
|
+
try {
|
|
40
|
+
url = new URL(req.url ?? '/', 'http://localhost');
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
send(400, { error: 'bad_request' });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// POST /agent-hook/approval — the Claude PreToolUse hook asks the user.
|
|
47
|
+
if (req.method === 'POST' && url.pathname === '/agent-hook/approval') {
|
|
48
|
+
if (!options.onHookApproval) {
|
|
49
|
+
send(404, { error: 'hooks_disabled' });
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const token = req.headers['x-uxnan-hook-token'];
|
|
53
|
+
readBody(req)
|
|
54
|
+
.then((raw) => {
|
|
55
|
+
let body;
|
|
56
|
+
try {
|
|
57
|
+
body = JSON.parse(raw || '{}');
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
send(400, { error: 'bad_json' });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
void options.onHookApproval(body, typeof token === 'string' ? token : undefined)
|
|
64
|
+
.then((result) => send(result.status, result.json))
|
|
65
|
+
.catch(() => send(500, { error: 'hook_error' }));
|
|
66
|
+
})
|
|
67
|
+
.catch(() => send(400, { error: 'read_error' }));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (req.method !== 'GET' || url.pathname !== '/pair/resolve') {
|
|
71
|
+
send(404, { error: 'not_found' });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (!options.onPairResolve) {
|
|
75
|
+
send(404, { error: 'pairing_disabled' });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const code = url.searchParams.get('code');
|
|
79
|
+
if (!code) {
|
|
80
|
+
send(400, { error: 'missing_code' });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const ip = req.socket.remoteAddress ?? 'unknown';
|
|
84
|
+
const result = options.onPairResolve(code, ip);
|
|
85
|
+
send(result.status, result.json);
|
|
86
|
+
}
|
|
87
|
+
/** Collect a request body (capped at 1 MiB) as a UTF-8 string. */
|
|
88
|
+
function readBody(req) {
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
let data = '';
|
|
91
|
+
let size = 0;
|
|
92
|
+
req.on('data', (chunk) => {
|
|
93
|
+
size += chunk.length;
|
|
94
|
+
if (size > 1_048_576) {
|
|
95
|
+
reject(new Error('body too large'));
|
|
96
|
+
req.destroy();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
data += chunk.toString('utf-8');
|
|
100
|
+
});
|
|
101
|
+
req.on('end', () => resolve(data));
|
|
102
|
+
req.on('error', reject);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=lan-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lan-server.js","sourceRoot":"","sources":["../../../src/transport/lan-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enumerate the bridge's directly-reachable `host:port` addresses so they can be
|
|
3
|
+
* advertised in the pairing QR. The phone tries these FIRST (direct LAN/Tailscale)
|
|
4
|
+
* and falls back to the relay.
|
|
5
|
+
*
|
|
6
|
+
* Includes every non-internal IPv4 the machine has — the LAN address(es) and, when
|
|
7
|
+
* present, a Tailscale `100.x` address (its interface is a normal non-internal
|
|
8
|
+
* IPv4, so a phone on the same tailnet connects directly with no hosted relay).
|
|
9
|
+
*
|
|
10
|
+
* Pure over an injected `networkInterfaces()` result so it can be unit-tested.
|
|
11
|
+
*/
|
|
12
|
+
import { type NetworkInterfaceInfo } from 'node:os';
|
|
13
|
+
export type InterfaceMap = NodeJS.Dict<NetworkInterfaceInfo[]>;
|
|
14
|
+
/** The non-internal IPv4 addresses (no port) — e.g. for mDNS A records. */
|
|
15
|
+
export declare function localIPv4s(ifaces?: InterfaceMap): string[];
|
|
16
|
+
/** Build `host:port` strings from the non-internal IPv4 addresses in `ifaces`. */
|
|
17
|
+
export declare function localHostPorts(port: number, ifaces?: InterfaceMap): string[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enumerate the bridge's directly-reachable `host:port` addresses so they can be
|
|
3
|
+
* advertised in the pairing QR. The phone tries these FIRST (direct LAN/Tailscale)
|
|
4
|
+
* and falls back to the relay.
|
|
5
|
+
*
|
|
6
|
+
* Includes every non-internal IPv4 the machine has — the LAN address(es) and, when
|
|
7
|
+
* present, a Tailscale `100.x` address (its interface is a normal non-internal
|
|
8
|
+
* IPv4, so a phone on the same tailnet connects directly with no hosted relay).
|
|
9
|
+
*
|
|
10
|
+
* Pure over an injected `networkInterfaces()` result so it can be unit-tested.
|
|
11
|
+
*/
|
|
12
|
+
import { networkInterfaces } from 'node:os';
|
|
13
|
+
/** The non-internal IPv4 addresses (no port) — e.g. for mDNS A records. */
|
|
14
|
+
export function localIPv4s(ifaces = networkInterfaces()) {
|
|
15
|
+
const addrs = new Set();
|
|
16
|
+
for (const infos of Object.values(ifaces)) {
|
|
17
|
+
for (const info of infos ?? []) {
|
|
18
|
+
// Node 18+ may report `family` as the string 'IPv4' or the number 4.
|
|
19
|
+
const isIPv4 = info.family === 'IPv4' || info.family === 4;
|
|
20
|
+
if (isIPv4 && !info.internal && info.address)
|
|
21
|
+
addrs.add(info.address);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return [...addrs].sort();
|
|
25
|
+
}
|
|
26
|
+
/** Build `host:port` strings from the non-internal IPv4 addresses in `ifaces`. */
|
|
27
|
+
export function localHostPorts(port, ifaces = networkInterfaces()) {
|
|
28
|
+
return localIPv4s(ifaces).map((address) => `${address}:${port}`);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=local-hosts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-hosts.js","sourceRoot":"","sources":["../../../src/transport/local-hosts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,iBAAiB,EAA6B,MAAM,SAAS,CAAC;AAIvE,2EAA2E;AAC3E,MAAM,UAAU,UAAU,CAAC,SAAuB,iBAAiB,EAAE;IACnE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1C,KAAK,MAAM,IAAI,IAAI,KAAK,IAAI,EAAE,EAAE,CAAC;YAC/B,qEAAqE;YACrE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,KAAK,MAAM,IAAK,IAAI,CAAC,MAAkB,KAAK,CAAC,CAAC;YACxE,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO;gBAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,SAAuB,iBAAiB,EAAE;IACrF,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,OAAO,IAAI,IAAI,EAAE,CAAC,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal mDNS / DNS-SD advertiser (RFC 6762 / 6763) — Phase 2 of manual-code
|
|
3
|
+
* pairing. The bridge announces a `_uxnan._tcp.local` service on the LAN so the
|
|
4
|
+
* phone can DISCOVER it (no typing the host); the user then enters the pairing
|
|
5
|
+
* code shown on the PC and the phone calls `GET /pair/resolve?code=` (Phase 1).
|
|
6
|
+
*
|
|
7
|
+
* Deliberately dependency-free (hand-rolled over `node:dgram`): the bridge ships
|
|
8
|
+
* as a global npm install / single binary, so we avoid a third-party mDNS stack
|
|
9
|
+
* (no native build, no supply-chain surface). Scope is bounded — advertise ONE
|
|
10
|
+
* service type and answer the standard browse queries; this is NOT a general mDNS
|
|
11
|
+
* implementation.
|
|
12
|
+
*
|
|
13
|
+
* Records advertised (in response to a PTR browse, plus unsolicited announcements):
|
|
14
|
+
* - PTR `_uxnan._tcp.local` → `<instance>._uxnan._tcp.local`
|
|
15
|
+
* - SRV `<instance>._uxnan._tcp.local` → 0 0 <port> `<host>.local`
|
|
16
|
+
* - TXT `<instance>._uxnan._tcp.local` → v=1, id=<deviceId>, port=<port>, addr=<ip>
|
|
17
|
+
* - A `<host>.local` → <ipv4>
|
|
18
|
+
* The TXT carries `addr`/`port` too, so a phone can connect without resolving the
|
|
19
|
+
* `.local` A record. It is best-effort: a failed bind (port 5353 busy, no perms)
|
|
20
|
+
* degrades silently — pairing still works by typing the host.
|
|
21
|
+
*
|
|
22
|
+
* Security: the advertisement carries only non-secret discovery hints (name,
|
|
23
|
+
* port, device id). The pairing CODE is never advertised — it stays the consent
|
|
24
|
+
* gate handed out on screen. See bridge/FOR-DEV.md.
|
|
25
|
+
*/
|
|
26
|
+
import { type RemoteInfo } from 'node:dgram';
|
|
27
|
+
import type { Logger } from '../logger.js';
|
|
28
|
+
/** Minimal UDP socket surface the advertiser needs (so it can be faked in tests). */
|
|
29
|
+
export interface UdpSocketLike {
|
|
30
|
+
on(event: 'message', listener: (msg: Buffer, rinfo: RemoteInfo) => void): void;
|
|
31
|
+
on(event: 'error', listener: (err: Error) => void): void;
|
|
32
|
+
bind(options: {
|
|
33
|
+
port: number;
|
|
34
|
+
exclusive?: boolean;
|
|
35
|
+
}, callback?: () => void): void;
|
|
36
|
+
addMembership(multicastAddress: string): void;
|
|
37
|
+
setMulticastTTL(ttl: number): void;
|
|
38
|
+
send(msg: Buffer, port: number, address: string, callback?: (err: Error | null) => void): void;
|
|
39
|
+
close(callback?: () => void): void;
|
|
40
|
+
}
|
|
41
|
+
export interface MdnsAdvertiserOptions {
|
|
42
|
+
/** Service instance name (the bridge display name; a single DNS-SD label). */
|
|
43
|
+
instanceName: string;
|
|
44
|
+
/** Host label for the A/SRV target (`<host>.local`); typically the machine name. */
|
|
45
|
+
hostName: string;
|
|
46
|
+
/** TCP port the LAN server listens on. */
|
|
47
|
+
port: number;
|
|
48
|
+
/** Non-internal IPv4 addresses to advertise (A records + TXT `addr`). */
|
|
49
|
+
addresses: string[];
|
|
50
|
+
/** Extra TXT key/values (e.g. `{ id: macDeviceId }`). */
|
|
51
|
+
txt?: Record<string, string>;
|
|
52
|
+
logger?: Logger;
|
|
53
|
+
/** Socket factory (tests inject a fake). */
|
|
54
|
+
socketFactory?: () => UdpSocketLike;
|
|
55
|
+
/** How many unsolicited announcements to send on start (default 2). */
|
|
56
|
+
announceCount?: number;
|
|
57
|
+
}
|
|
58
|
+
export declare class MdnsAdvertiser {
|
|
59
|
+
#private;
|
|
60
|
+
constructor(options: MdnsAdvertiserOptions);
|
|
61
|
+
/** Bind, join the multicast group, announce, and answer browse queries. Best-effort. */
|
|
62
|
+
start(): void;
|
|
63
|
+
/** Send a goodbye (TTL 0) and close. Best-effort. */
|
|
64
|
+
stop(): void;
|
|
65
|
+
}
|
|
66
|
+
interface Question {
|
|
67
|
+
labels: string[];
|
|
68
|
+
type: number;
|
|
69
|
+
}
|
|
70
|
+
interface ResourceRecord {
|
|
71
|
+
labels: string[];
|
|
72
|
+
type: number;
|
|
73
|
+
flush: boolean;
|
|
74
|
+
ttl: number;
|
|
75
|
+
rdata: Buffer;
|
|
76
|
+
}
|
|
77
|
+
/** Encode a domain name as a sequence of length-prefixed labels + a 0 terminator. */
|
|
78
|
+
export declare function encodeName(labels: string[]): Buffer;
|
|
79
|
+
/** Build an mDNS response message (QR=1, AA=1) carrying the given answer records. */
|
|
80
|
+
export declare function buildMessage(records: ResourceRecord[]): Buffer;
|
|
81
|
+
/** Parse just the question section of a DNS message into label arrays + qtype. */
|
|
82
|
+
export declare function parseQuestions(msg: Buffer): Question[];
|
|
83
|
+
export {};
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal mDNS / DNS-SD advertiser (RFC 6762 / 6763) — Phase 2 of manual-code
|
|
3
|
+
* pairing. The bridge announces a `_uxnan._tcp.local` service on the LAN so the
|
|
4
|
+
* phone can DISCOVER it (no typing the host); the user then enters the pairing
|
|
5
|
+
* code shown on the PC and the phone calls `GET /pair/resolve?code=` (Phase 1).
|
|
6
|
+
*
|
|
7
|
+
* Deliberately dependency-free (hand-rolled over `node:dgram`): the bridge ships
|
|
8
|
+
* as a global npm install / single binary, so we avoid a third-party mDNS stack
|
|
9
|
+
* (no native build, no supply-chain surface). Scope is bounded — advertise ONE
|
|
10
|
+
* service type and answer the standard browse queries; this is NOT a general mDNS
|
|
11
|
+
* implementation.
|
|
12
|
+
*
|
|
13
|
+
* Records advertised (in response to a PTR browse, plus unsolicited announcements):
|
|
14
|
+
* - PTR `_uxnan._tcp.local` → `<instance>._uxnan._tcp.local`
|
|
15
|
+
* - SRV `<instance>._uxnan._tcp.local` → 0 0 <port> `<host>.local`
|
|
16
|
+
* - TXT `<instance>._uxnan._tcp.local` → v=1, id=<deviceId>, port=<port>, addr=<ip>
|
|
17
|
+
* - A `<host>.local` → <ipv4>
|
|
18
|
+
* The TXT carries `addr`/`port` too, so a phone can connect without resolving the
|
|
19
|
+
* `.local` A record. It is best-effort: a failed bind (port 5353 busy, no perms)
|
|
20
|
+
* degrades silently — pairing still works by typing the host.
|
|
21
|
+
*
|
|
22
|
+
* Security: the advertisement carries only non-secret discovery hints (name,
|
|
23
|
+
* port, device id). The pairing CODE is never advertised — it stays the consent
|
|
24
|
+
* gate handed out on screen. See bridge/FOR-DEV.md.
|
|
25
|
+
*/
|
|
26
|
+
import { createSocket } from 'node:dgram';
|
|
27
|
+
const MDNS_ADDRESS = '224.0.0.251';
|
|
28
|
+
const MDNS_PORT = 5353;
|
|
29
|
+
const SERVICE_TYPE_LABELS = ['_uxnan', '_tcp', 'local'];
|
|
30
|
+
const DNSSD_META_LABELS = ['_services', '_dns-sd', '_udp', 'local'];
|
|
31
|
+
const TYPE_A = 1;
|
|
32
|
+
const TYPE_PTR = 12;
|
|
33
|
+
const TYPE_TXT = 16;
|
|
34
|
+
const TYPE_SRV = 33;
|
|
35
|
+
const CLASS_IN = 1;
|
|
36
|
+
const FLUSH = 0x8000; // cache-flush bit for unique records (A/SRV/TXT)
|
|
37
|
+
const DEFAULT_TTL = 120;
|
|
38
|
+
export class MdnsAdvertiser {
|
|
39
|
+
#opts;
|
|
40
|
+
#logger;
|
|
41
|
+
#socket;
|
|
42
|
+
#started = false;
|
|
43
|
+
constructor(options) {
|
|
44
|
+
this.#opts = options;
|
|
45
|
+
this.#logger = options.logger;
|
|
46
|
+
}
|
|
47
|
+
/** Bind, join the multicast group, announce, and answer browse queries. Best-effort. */
|
|
48
|
+
start() {
|
|
49
|
+
if (this.#started)
|
|
50
|
+
return;
|
|
51
|
+
try {
|
|
52
|
+
const socket = (this.#opts.socketFactory ?? defaultSocketFactory)();
|
|
53
|
+
this.#socket = socket;
|
|
54
|
+
socket.on('error', (err) => {
|
|
55
|
+
this.#logger?.warn(`mDNS socket error: ${err.message}`);
|
|
56
|
+
this.stop();
|
|
57
|
+
});
|
|
58
|
+
socket.on('message', (msg) => this.#onMessage(msg));
|
|
59
|
+
socket.bind({ port: MDNS_PORT, exclusive: false }, () => {
|
|
60
|
+
try {
|
|
61
|
+
socket.addMembership(MDNS_ADDRESS);
|
|
62
|
+
socket.setMulticastTTL(255);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
this.#logger?.warn(`mDNS membership failed: ${errMsg(err)}`);
|
|
66
|
+
}
|
|
67
|
+
this.#announce();
|
|
68
|
+
});
|
|
69
|
+
this.#started = true;
|
|
70
|
+
this.#logger?.info(`mDNS advertising _uxnan._tcp on :${this.#opts.port}`);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
this.#logger?.warn(`mDNS advertise disabled: ${errMsg(err)}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Send a goodbye (TTL 0) and close. Best-effort. */
|
|
77
|
+
stop() {
|
|
78
|
+
const socket = this.#socket;
|
|
79
|
+
if (!socket)
|
|
80
|
+
return;
|
|
81
|
+
this.#socket = undefined;
|
|
82
|
+
this.#started = false;
|
|
83
|
+
try {
|
|
84
|
+
socket.send(this.#buildResponse(0), MDNS_PORT, MDNS_ADDRESS, () => socket.close());
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
try {
|
|
88
|
+
socket.close();
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
/* already closed */
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
#announce() {
|
|
96
|
+
const times = this.#opts.announceCount ?? 2;
|
|
97
|
+
for (let i = 0; i < times; i += 1)
|
|
98
|
+
this.#sendResponse();
|
|
99
|
+
}
|
|
100
|
+
#onMessage(msg) {
|
|
101
|
+
let questions;
|
|
102
|
+
try {
|
|
103
|
+
questions = parseQuestions(msg);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Respond if any question targets our service type, the DNS-SD meta-query, our
|
|
109
|
+
// instance, or our host record.
|
|
110
|
+
const wantsUs = questions.some((q) => {
|
|
111
|
+
const n = q.labels;
|
|
112
|
+
return (labelsEqual(n, SERVICE_TYPE_LABELS) ||
|
|
113
|
+
labelsEqual(n, DNSSD_META_LABELS) ||
|
|
114
|
+
labelsEqual(n, this.#instanceLabels()) ||
|
|
115
|
+
labelsEqual(n, this.#hostLabels()));
|
|
116
|
+
});
|
|
117
|
+
if (wantsUs)
|
|
118
|
+
this.#sendResponse();
|
|
119
|
+
}
|
|
120
|
+
#sendResponse() {
|
|
121
|
+
const socket = this.#socket;
|
|
122
|
+
if (!socket)
|
|
123
|
+
return;
|
|
124
|
+
try {
|
|
125
|
+
socket.send(this.#buildResponse(DEFAULT_TTL), MDNS_PORT, MDNS_ADDRESS);
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
this.#logger?.warn(`mDNS send failed: ${errMsg(err)}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
#instanceLabels() {
|
|
132
|
+
return [this.#opts.instanceName, ...SERVICE_TYPE_LABELS];
|
|
133
|
+
}
|
|
134
|
+
#hostLabels() {
|
|
135
|
+
return [this.#opts.hostName, 'local'];
|
|
136
|
+
}
|
|
137
|
+
/** Build the full announcement/response message (PTR + SRV + TXT + A). */
|
|
138
|
+
#buildResponse(ttl) {
|
|
139
|
+
const instance = this.#instanceLabels();
|
|
140
|
+
const host = this.#hostLabels();
|
|
141
|
+
const txt = {
|
|
142
|
+
v: '1',
|
|
143
|
+
port: String(this.#opts.port),
|
|
144
|
+
...(this.#opts.addresses[0] ? { addr: this.#opts.addresses[0] } : {}),
|
|
145
|
+
...this.#opts.txt,
|
|
146
|
+
};
|
|
147
|
+
const records = [
|
|
148
|
+
{
|
|
149
|
+
labels: SERVICE_TYPE_LABELS,
|
|
150
|
+
type: TYPE_PTR,
|
|
151
|
+
flush: false,
|
|
152
|
+
ttl,
|
|
153
|
+
rdata: encodeName(instance),
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
labels: instance,
|
|
157
|
+
type: TYPE_SRV,
|
|
158
|
+
flush: true,
|
|
159
|
+
ttl,
|
|
160
|
+
rdata: encodeSrv(0, 0, this.#opts.port, host),
|
|
161
|
+
},
|
|
162
|
+
{ labels: instance, type: TYPE_TXT, flush: true, ttl, rdata: encodeTxt(txt) },
|
|
163
|
+
...this.#opts.addresses.map((addr) => ({
|
|
164
|
+
labels: host,
|
|
165
|
+
type: TYPE_A,
|
|
166
|
+
flush: true,
|
|
167
|
+
ttl,
|
|
168
|
+
rdata: encodeIPv4(addr),
|
|
169
|
+
})),
|
|
170
|
+
];
|
|
171
|
+
return buildMessage(records);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function defaultSocketFactory() {
|
|
175
|
+
return createSocket({ type: 'udp4', reuseAddr: true });
|
|
176
|
+
}
|
|
177
|
+
/** Encode a domain name as a sequence of length-prefixed labels + a 0 terminator. */
|
|
178
|
+
export function encodeName(labels) {
|
|
179
|
+
const parts = [];
|
|
180
|
+
for (const label of labels) {
|
|
181
|
+
const bytes = Buffer.from(label, 'utf-8');
|
|
182
|
+
if (bytes.length > 63)
|
|
183
|
+
throw new Error('label too long');
|
|
184
|
+
parts.push(Buffer.from([bytes.length]), bytes);
|
|
185
|
+
}
|
|
186
|
+
parts.push(Buffer.from([0]));
|
|
187
|
+
return Buffer.concat(parts);
|
|
188
|
+
}
|
|
189
|
+
function encodeSrv(priority, weight, port, target) {
|
|
190
|
+
const head = Buffer.alloc(6);
|
|
191
|
+
head.writeUInt16BE(priority, 0);
|
|
192
|
+
head.writeUInt16BE(weight, 2);
|
|
193
|
+
head.writeUInt16BE(port, 4);
|
|
194
|
+
return Buffer.concat([head, encodeName(target)]);
|
|
195
|
+
}
|
|
196
|
+
function encodeTxt(entries) {
|
|
197
|
+
const parts = [];
|
|
198
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
199
|
+
const s = Buffer.from(`${key}=${value}`, 'utf-8');
|
|
200
|
+
parts.push(Buffer.from([Math.min(s.length, 255)]), s.subarray(0, 255));
|
|
201
|
+
}
|
|
202
|
+
if (parts.length === 0)
|
|
203
|
+
parts.push(Buffer.from([0])); // empty TXT = one zero-length string
|
|
204
|
+
return Buffer.concat(parts);
|
|
205
|
+
}
|
|
206
|
+
function encodeIPv4(address) {
|
|
207
|
+
const octets = address.split('.').map((o) => Number.parseInt(o, 10));
|
|
208
|
+
if (octets.length !== 4 || octets.some((o) => !Number.isInteger(o) || o < 0 || o > 255)) {
|
|
209
|
+
throw new Error(`bad IPv4: ${address}`);
|
|
210
|
+
}
|
|
211
|
+
return Buffer.from(octets);
|
|
212
|
+
}
|
|
213
|
+
/** Build an mDNS response message (QR=1, AA=1) carrying the given answer records. */
|
|
214
|
+
export function buildMessage(records) {
|
|
215
|
+
const header = Buffer.alloc(12);
|
|
216
|
+
header.writeUInt16BE(0, 0); // ID
|
|
217
|
+
header.writeUInt16BE(0x8400, 2); // flags: QR + AA
|
|
218
|
+
header.writeUInt16BE(0, 4); // QDCOUNT
|
|
219
|
+
header.writeUInt16BE(records.length, 6); // ANCOUNT
|
|
220
|
+
const body = [header];
|
|
221
|
+
for (const rr of records) {
|
|
222
|
+
const name = encodeName(rr.labels);
|
|
223
|
+
const meta = Buffer.alloc(10);
|
|
224
|
+
meta.writeUInt16BE(rr.type, 0);
|
|
225
|
+
meta.writeUInt16BE(CLASS_IN | (rr.flush ? FLUSH : 0), 2);
|
|
226
|
+
meta.writeUInt32BE(rr.ttl, 4);
|
|
227
|
+
meta.writeUInt16BE(rr.rdata.length, 8);
|
|
228
|
+
body.push(name, meta, rr.rdata);
|
|
229
|
+
}
|
|
230
|
+
return Buffer.concat(body);
|
|
231
|
+
}
|
|
232
|
+
/** Parse just the question section of a DNS message into label arrays + qtype. */
|
|
233
|
+
export function parseQuestions(msg) {
|
|
234
|
+
if (msg.length < 12)
|
|
235
|
+
return [];
|
|
236
|
+
const qd = msg.readUInt16BE(4);
|
|
237
|
+
let offset = 12;
|
|
238
|
+
const questions = [];
|
|
239
|
+
for (let i = 0; i < qd; i += 1) {
|
|
240
|
+
const { labels, next } = readName(msg, offset);
|
|
241
|
+
offset = next;
|
|
242
|
+
if (offset + 4 > msg.length)
|
|
243
|
+
break;
|
|
244
|
+
const type = msg.readUInt16BE(offset);
|
|
245
|
+
offset += 4; // type(2) + class(2)
|
|
246
|
+
questions.push({ labels, type });
|
|
247
|
+
}
|
|
248
|
+
return questions;
|
|
249
|
+
}
|
|
250
|
+
/** Read a (possibly compressed) domain name; returns labels + the offset after it. */
|
|
251
|
+
function readName(msg, start) {
|
|
252
|
+
const labels = [];
|
|
253
|
+
let offset = start;
|
|
254
|
+
let next = -1;
|
|
255
|
+
let guard = 0;
|
|
256
|
+
for (;;) {
|
|
257
|
+
if (guard++ > 128 || offset >= msg.length)
|
|
258
|
+
break;
|
|
259
|
+
const len = msg[offset];
|
|
260
|
+
if (len === 0) {
|
|
261
|
+
offset += 1;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
if ((len & 0xc0) === 0xc0) {
|
|
265
|
+
// compression pointer (2 bytes); the name continues at the pointed offset
|
|
266
|
+
if (next === -1)
|
|
267
|
+
next = offset + 2;
|
|
268
|
+
offset = ((len & 0x3f) << 8) | msg[offset + 1];
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
labels.push(msg.toString('utf-8', offset + 1, offset + 1 + len));
|
|
272
|
+
offset += 1 + len;
|
|
273
|
+
}
|
|
274
|
+
return { labels, next: next === -1 ? offset : next };
|
|
275
|
+
}
|
|
276
|
+
function labelsEqual(a, b) {
|
|
277
|
+
return (a.length === b.length && a.every((label, i) => label.toLowerCase() === b[i].toLowerCase()));
|
|
278
|
+
}
|
|
279
|
+
function errMsg(err) {
|
|
280
|
+
return err instanceof Error ? err.message : String(err);
|
|
281
|
+
}
|
|
282
|
+
//# sourceMappingURL=mdns-advertiser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mdns-advertiser.js","sourceRoot":"","sources":["../../../src/transport/mdns-advertiser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,YAAY,EAAgC,MAAM,YAAY,CAAC;AAGxE,MAAM,YAAY,GAAG,aAAa,CAAC;AACnC,MAAM,SAAS,GAAG,IAAI,CAAC;AACvB,MAAM,mBAAmB,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AACxD,MAAM,iBAAiB,GAAG,CAAC,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpE,MAAM,MAAM,GAAG,CAAC,CAAC;AACjB,MAAM,QAAQ,GAAG,EAAE,CAAC;AACpB,MAAM,QAAQ,GAAG,EAAE,CAAC;AACpB,MAAM,QAAQ,GAAG,EAAE,CAAC;AACpB,MAAM,QAAQ,GAAG,CAAC,CAAC;AACnB,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,iDAAiD;AACvE,MAAM,WAAW,GAAG,GAAG,CAAC;AA+BxB,MAAM,OAAO,cAAc;IAChB,KAAK,CAAwB;IAC7B,OAAO,CAAqB;IACrC,OAAO,CAA4B;IACnC,QAAQ,GAAG,KAAK,CAAC;IAEjB,YAAY,OAA8B;QACxC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAChC,CAAC;IAED,wFAAwF;IACxF,KAAK;QACH,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,IAAI,oBAAoB,CAAC,EAAE,CAAC;YACpE,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;YACtB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACzB,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,sBAAsB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;gBACxD,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,CAAC,CAAC,CAAC;YACH,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;YACpD,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE;gBACtD,IAAI,CAAC;oBACH,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;oBACnC,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;gBAC9B,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,2BAA2B,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBAC/D,CAAC;gBACD,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACrB,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,oCAAoC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,4BAA4B,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IAED,qDAAqD;IACrD,IAAI;QACF,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;QACzB,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;QACrF,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC;gBACH,MAAM,CAAC,KAAK,EAAE,CAAC;YACjB,CAAC;YAAC,MAAM,CAAC;gBACP,oBAAoB;YACtB,CAAC;QACH,CAAC;IACH,CAAC;IAED,SAAS;QACP,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,IAAI,CAAC,CAAC;QAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,CAAC;YAAE,IAAI,CAAC,aAAa,EAAE,CAAC;IAC1D,CAAC;IAED,UAAU,CAAC,GAAW;QACpB,IAAI,SAAqB,CAAC;QAC1B,IAAI,CAAC;YACH,SAAS,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,+EAA+E;QAC/E,gCAAgC;QAChC,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE;YACnC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;YACnB,OAAO,CACL,WAAW,CAAC,CAAC,EAAE,mBAAmB,CAAC;gBACnC,WAAW,CAAC,CAAC,EAAE,iBAAiB,CAAC;gBACjC,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC;gBACtC,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CACnC,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,IAAI,OAAO;YAAE,IAAI,CAAC,aAAa,EAAE,CAAC;IACpC,CAAC;IAED,aAAa;QACX,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,IAAI,CAAC;YACH,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;QACzE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,qBAAqB,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;IAED,eAAe;QACb,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,GAAG,mBAAmB,CAAC,CAAC;IAC3D,CAAC;IAED,WAAW;QACT,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACxC,CAAC;IAED,0EAA0E;IAC1E,cAAc,CAAC,GAAW;QACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAChC,MAAM,GAAG,GAAG;YACV,CAAC,EAAE,GAAG;YACN,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;YAC7B,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG;SAClB,CAAC;QACF,MAAM,OAAO,GAAqB;YAChC;gBACE,MAAM,EAAE,mBAAmB;gBAC3B,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,KAAK;gBACZ,GAAG;gBACH,KAAK,EAAE,UAAU,CAAC,QAAQ,CAAC;aAC5B;YACD;gBACE,MAAM,EAAE,QAAQ;gBAChB,IAAI,EAAE,QAAQ;gBACd,KAAK,EAAE,IAAI;gBACX,GAAG;gBACH,KAAK,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC;aAC9C;YACD,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE;YAC7E,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACrC,MAAM,EAAE,IAAI;gBACZ,IAAI,EAAE,MAAM;gBACZ,KAAK,EAAE,IAAI;gBACX,GAAG;gBACH,KAAK,EAAE,UAAU,CAAC,IAAI,CAAC;aACxB,CAAC,CAAC;SACJ,CAAC;QACF,OAAO,YAAY,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;CACF;AAED,SAAS,oBAAoB;IAC3B,OAAO,YAAY,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,CAAuC,CAAC;AAC/F,CAAC;AAiBD,qFAAqF;AACrF,MAAM,UAAU,UAAU,CAAC,MAAgB;IACzC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC1C,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;QACzD,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACjD,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7B,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,SAAS,CAAC,QAAgB,EAAE,MAAc,EAAE,IAAY,EAAE,MAAgB;IACjF,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7B,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAChC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC9B,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC5B,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AACnD,CAAC;AAED,SAAS,SAAS,CAAC,OAA+B;IAChD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,KAAK,EAAE,EAAE,OAAO,CAAC,CAAC;QAClD,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,qCAAqC;IAC3F,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,UAAU,CAAC,OAAe;IACjC,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;IACrE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;QACxF,MAAM,IAAI,KAAK,CAAC,aAAa,OAAO,EAAE,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC7B,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,YAAY,CAAC,OAAyB;IACpD,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAChC,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK;IACjC,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,iBAAiB;IAClD,MAAM,CAAC,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU;IACtC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU;IACnD,MAAM,IAAI,GAAa,CAAC,MAAM,CAAC,CAAC;IAChC,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QAC/B,IAAI,CAAC,aAAa,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACzD,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC9B,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,GAAG,CAAC,MAAM,GAAG,EAAE;QAAE,OAAO,EAAE,CAAC;IAC/B,MAAM,EAAE,GAAG,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC/B,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,MAAM,SAAS,GAAe,EAAE,CAAC;IACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC/C,MAAM,GAAG,IAAI,CAAC;QACd,IAAI,MAAM,GAAG,CAAC,GAAG,GAAG,CAAC,MAAM;YAAE,MAAM;QACnC,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;QACtC,MAAM,IAAI,CAAC,CAAC,CAAC,qBAAqB;QAClC,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,sFAAsF;AACtF,SAAS,QAAQ,CAAC,GAAW,EAAE,KAAa;IAC1C,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC;IACd,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,SAAS,CAAC;QACR,IAAI,KAAK,EAAE,GAAG,GAAG,IAAI,MAAM,IAAI,GAAG,CAAC,MAAM;YAAE,MAAM;QACjD,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAE,CAAC;QACzB,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC;YACd,MAAM,IAAI,CAAC,CAAC;YACZ,MAAM;QACR,CAAC;QACD,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YAC1B,0EAA0E;YAC1E,IAAI,IAAI,KAAK,CAAC,CAAC;gBAAE,IAAI,GAAG,MAAM,GAAG,CAAC,CAAC;YACnC,MAAM,GAAG,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;YAChD,SAAS;QACX,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;QACjE,MAAM,IAAI,CAAC,GAAG,GAAG,CAAC;IACpB,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACvD,CAAC;AAED,SAAS,WAAW,CAAC,CAAW,EAAE,CAAW;IAC3C,OAAO,CACL,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,CAAC,CAC5F,CAAC;AACJ,CAAC;AAED,SAAS,MAAM,CAAC,GAAY;IAC1B,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport-agnostic binary message channel. Both the relay client and the LAN
|
|
3
|
+
* server adapt their WebSocket to this interface so the secure session logic is
|
|
4
|
+
* written once.
|
|
5
|
+
*
|
|
6
|
+
* Frames are raw bytes: UTF-8 JSON for handshake control messages and encrypted
|
|
7
|
+
* envelopes (matching the mobile app, which sends binary frames).
|
|
8
|
+
*/
|
|
9
|
+
export interface MessageIO {
|
|
10
|
+
/** Send a binary frame. */
|
|
11
|
+
send(bytes: Buffer): void;
|
|
12
|
+
/** Register an inbound-frame listener. */
|
|
13
|
+
onMessage(listener: (bytes: Buffer) => void): void;
|
|
14
|
+
/** Register a close listener. */
|
|
15
|
+
onClose(listener: () => void): void;
|
|
16
|
+
/** Close the underlying channel. */
|
|
17
|
+
close(): void;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* An async FIFO over inbound frames. `next()` resolves with the next frame or
|
|
21
|
+
* rejects once the channel is closed and drained. Used to drive the request/
|
|
22
|
+
* response handshake sequentially.
|
|
23
|
+
*/
|
|
24
|
+
export declare class MessageQueue {
|
|
25
|
+
#private;
|
|
26
|
+
push(bytes: Buffer): void;
|
|
27
|
+
close(): void;
|
|
28
|
+
next(): Promise<Buffer>;
|
|
29
|
+
}
|
|
30
|
+
/** Attach a {@link MessageQueue} to a {@link MessageIO}. */
|
|
31
|
+
export declare function queueFor(io: MessageIO): MessageQueue;
|
|
32
|
+
/** Create a connected in-memory pair of {@link MessageIO}s (for tests). */
|
|
33
|
+
export declare function createInMemoryIoPair(): [MessageIO, MessageIO];
|