dns-sd-browser 1.0.0
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/LICENSE +21 -0
- package/README.md +670 -0
- package/dist/browser.d.ts +145 -0
- package/dist/constants.d.ts +12 -0
- package/dist/dns.d.ts +155 -0
- package/dist/index.d.ts +113 -0
- package/dist/service.d.ts +115 -0
- package/dist/transport.d.ts +67 -0
- package/lib/browser.js +1661 -0
- package/lib/constants.js +17 -0
- package/lib/dns.js +685 -0
- package/lib/index.js +252 -0
- package/lib/service.js +152 -0
- package/lib/transport.js +345 -0
- package/package.json +44 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
export class ServiceBrowser {
|
|
2
|
+
/**
|
|
3
|
+
* @param {MdnsTransport} transport
|
|
4
|
+
* @param {object} options
|
|
5
|
+
* @param {string} options.queryName - e.g. "_http._tcp.local"
|
|
6
|
+
* @param {string} options.serviceType - e.g. "_http._tcp"
|
|
7
|
+
* @param {string} options.domain - e.g. "local"
|
|
8
|
+
* @param {string} options.protocol - e.g. "tcp"
|
|
9
|
+
* @param {boolean} [options.isTypeEnumeration] - true for browseAll
|
|
10
|
+
* @param {AbortSignal} [options.signal]
|
|
11
|
+
* @param {() => void} [options.onDestroy] - Called when the browser is destroyed
|
|
12
|
+
* @param {number} [options.reconfirmTimeoutMs] - Reconfirmation timeout (ms), default 10000
|
|
13
|
+
* @param {number} [options.poofTimeoutMs=10000] - POOF window (RFC 6762 §10.5)
|
|
14
|
+
* @param {number} [options.poofResponseWaitMs=2000] - Time to wait for response before counting as unanswered
|
|
15
|
+
*/
|
|
16
|
+
constructor(transport: MdnsTransport, { queryName, serviceType, domain, protocol, isTypeEnumeration, signal, onDestroy, reconfirmTimeoutMs, poofTimeoutMs, poofResponseWaitMs }: {
|
|
17
|
+
queryName: string;
|
|
18
|
+
serviceType: string;
|
|
19
|
+
domain: string;
|
|
20
|
+
protocol: string;
|
|
21
|
+
isTypeEnumeration?: boolean | undefined;
|
|
22
|
+
signal?: AbortSignal | undefined;
|
|
23
|
+
onDestroy?: (() => void) | undefined;
|
|
24
|
+
reconfirmTimeoutMs?: number | undefined;
|
|
25
|
+
poofTimeoutMs?: number | undefined;
|
|
26
|
+
poofResponseWaitMs?: number | undefined;
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Currently discovered services, keyed by FQDN.
|
|
30
|
+
* @type {Map<string, Service>}
|
|
31
|
+
*/
|
|
32
|
+
services: Map<string, Service>;
|
|
33
|
+
/**
|
|
34
|
+
* Manually remove a service by FQDN, emitting a `serviceDown` event.
|
|
35
|
+
*
|
|
36
|
+
* Use this when your application detects that a service is unreachable
|
|
37
|
+
* (e.g. via a health check) before its TTL expires. The service is removed
|
|
38
|
+
* from the `services` Map and its known-answer record is cleared, so it
|
|
39
|
+
* will be re-discovered if the advertiser announces it again.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} fqdn - Fully qualified service name (e.g. "My Service._http._tcp.local")
|
|
42
|
+
* @returns {boolean} true if the service was found and removed, false otherwise
|
|
43
|
+
*/
|
|
44
|
+
removeService(fqdn: string): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Request reconfirmation of a service record (RFC 6762 §10.4).
|
|
47
|
+
* Sends verification queries and removes the service if no response
|
|
48
|
+
* is received within the timeout.
|
|
49
|
+
* @param {string} fqdn - The service FQDN to reconfirm
|
|
50
|
+
*/
|
|
51
|
+
reconfirm(fqdn: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* Flush all discovered services and restart querying from scratch.
|
|
54
|
+
*
|
|
55
|
+
* Call this after a network interface change (e.g. WiFi reconnect).
|
|
56
|
+
* All current services are emitted as `serviceDown` events, caches are
|
|
57
|
+
* cleared, and querying restarts with the initial rapid schedule.
|
|
58
|
+
*/
|
|
59
|
+
resetNetwork(): void;
|
|
60
|
+
/**
|
|
61
|
+
* Stop browsing and end the async iterator.
|
|
62
|
+
* @param {any} [reason] - If provided, the iterator throws this instead of
|
|
63
|
+
* returning done: true (matching Node.js convention for AbortSignal).
|
|
64
|
+
*/
|
|
65
|
+
destroy(reason?: any): void;
|
|
66
|
+
/** @returns {Promise<void>} */
|
|
67
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
68
|
+
/** @returns {AsyncIterableIterator<BrowseEvent>} */
|
|
69
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<BrowseEvent>;
|
|
70
|
+
#private;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* AllServiceBrowser — discovers all service instances on the network by
|
|
74
|
+
* first enumerating service types (RFC 6763 §9), then spawning a
|
|
75
|
+
* ServiceBrowser for each discovered type.
|
|
76
|
+
*
|
|
77
|
+
* Presents the same async iterable interface as ServiceBrowser, yielding
|
|
78
|
+
* fully resolved BrowseEvents with real host, port, and addresses.
|
|
79
|
+
*/
|
|
80
|
+
export class AllServiceBrowser {
|
|
81
|
+
/**
|
|
82
|
+
* @param {MdnsTransport} transport
|
|
83
|
+
* @param {object} options
|
|
84
|
+
* @param {string} options.domain
|
|
85
|
+
* @param {AbortSignal} [options.signal]
|
|
86
|
+
* @param {() => void} [options.onDestroy]
|
|
87
|
+
*/
|
|
88
|
+
constructor(transport: MdnsTransport, { domain, signal, onDestroy }: {
|
|
89
|
+
domain: string;
|
|
90
|
+
signal?: AbortSignal | undefined;
|
|
91
|
+
onDestroy?: (() => void) | undefined;
|
|
92
|
+
});
|
|
93
|
+
/**
|
|
94
|
+
* Merged live map of all discovered service instances across all types.
|
|
95
|
+
* @type {Map<string, Service>}
|
|
96
|
+
*/
|
|
97
|
+
services: Map<string, Service>;
|
|
98
|
+
/**
|
|
99
|
+
* Get the first discovered service. Resolves as soon as any service
|
|
100
|
+
* instance emits a serviceUp event.
|
|
101
|
+
* @returns {Promise<Service>}
|
|
102
|
+
*/
|
|
103
|
+
first(): Promise<Service>;
|
|
104
|
+
/**
|
|
105
|
+
* Flush all discovered services and restart querying from scratch.
|
|
106
|
+
*
|
|
107
|
+
* Call this after a network interface change (e.g. WiFi reconnect).
|
|
108
|
+
* Delegates to each internal ServiceBrowser and clears the merged
|
|
109
|
+
* services map.
|
|
110
|
+
*/
|
|
111
|
+
resetNetwork(): void;
|
|
112
|
+
/**
|
|
113
|
+
* Stop all sub-browsers and end the async iterator.
|
|
114
|
+
* @param {any} [reason] - If provided, the iterator throws this instead of
|
|
115
|
+
* returning done: true (matching Node.js convention for AbortSignal).
|
|
116
|
+
*/
|
|
117
|
+
destroy(reason?: any): void;
|
|
118
|
+
/** @returns {Promise<void>} */
|
|
119
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
120
|
+
/** @returns {AsyncIterableIterator<BrowseEvent>} */
|
|
121
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<BrowseEvent>;
|
|
122
|
+
#private;
|
|
123
|
+
}
|
|
124
|
+
export type Service = import("./service.js").Service;
|
|
125
|
+
export type DnsPacket = import("./dns.js").DnsPacket;
|
|
126
|
+
export type DnsRecord = import("./dns.js").DnsRecord;
|
|
127
|
+
export type MdnsTransport = import("./transport.js").MdnsTransport;
|
|
128
|
+
export type BrowseEvent = {
|
|
129
|
+
type: "serviceUp";
|
|
130
|
+
service: Service;
|
|
131
|
+
} | {
|
|
132
|
+
type: "serviceDown";
|
|
133
|
+
service: Service;
|
|
134
|
+
} | {
|
|
135
|
+
type: "serviceUpdated";
|
|
136
|
+
service: Service;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Resolves a promise externally — used for the async iterator's event queue.
|
|
140
|
+
*/
|
|
141
|
+
export type Deferred<T> = {
|
|
142
|
+
promise: Promise<T>;
|
|
143
|
+
resolve: (value: T) => void;
|
|
144
|
+
reject: (reason?: unknown) => void;
|
|
145
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** mDNS multicast IPv4 address (RFC 6762 §3) */
|
|
2
|
+
export const MDNS_ADDRESS: "224.0.0.251";
|
|
3
|
+
/** mDNS multicast IPv6 address (RFC 6762 §3) */
|
|
4
|
+
export const MDNS_ADDRESS_V6: "FF02::FB";
|
|
5
|
+
/** mDNS default port (RFC 6762 §3) */
|
|
6
|
+
export const MDNS_PORT: 5353;
|
|
7
|
+
/** mDNS multicast TTL — must be 255 per RFC 6762 §11 */
|
|
8
|
+
export const MDNS_TTL: 255;
|
|
9
|
+
/** Meta-query name for service type enumeration (RFC 6763 §9) */
|
|
10
|
+
export const SERVICE_TYPE_ENUMERATION: "_services._dns-sd._udp.local";
|
|
11
|
+
/** Default domain for mDNS */
|
|
12
|
+
export const DEFAULT_DOMAIN: "local";
|
package/dist/dns.d.ts
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {object} DnsFlags
|
|
3
|
+
* @property {boolean} qr - true for response, false for query
|
|
4
|
+
* @property {number} opcode
|
|
5
|
+
* @property {boolean} aa - Authoritative Answer
|
|
6
|
+
* @property {boolean} tc - Truncated
|
|
7
|
+
* @property {boolean} rd - Recursion Desired
|
|
8
|
+
* @property {boolean} ra - Recursion Available
|
|
9
|
+
* @property {number} rcode - Response code
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {object} DnsQuestion
|
|
13
|
+
* @property {string} name
|
|
14
|
+
* @property {number} type - RecordType value
|
|
15
|
+
* @property {boolean} [qu] - QU (unicast-response) bit (RFC 6762 §5.4)
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {object} SrvData
|
|
19
|
+
* @property {number} priority
|
|
20
|
+
* @property {number} weight
|
|
21
|
+
* @property {number} port
|
|
22
|
+
* @property {string} target
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {object} DnsRecord
|
|
26
|
+
* @property {string} name
|
|
27
|
+
* @property {number} type - RecordType value
|
|
28
|
+
* @property {number} class - Usually 1 (IN)
|
|
29
|
+
* @property {boolean} cacheFlush - mDNS cache-flush bit
|
|
30
|
+
* @property {number} ttl
|
|
31
|
+
* @property {string | SrvData | Uint8Array[]} data - Type-specific data
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {object} DnsPacket
|
|
35
|
+
* @property {number} id
|
|
36
|
+
* @property {DnsFlags} flags
|
|
37
|
+
* @property {DnsQuestion[]} questions
|
|
38
|
+
* @property {DnsRecord[]} answers
|
|
39
|
+
* @property {DnsRecord[]} authorities
|
|
40
|
+
* @property {DnsRecord[]} additionals
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Decode a DNS packet from a buffer.
|
|
44
|
+
* @param {Uint8Array} buf
|
|
45
|
+
* @returns {DnsPacket}
|
|
46
|
+
*/
|
|
47
|
+
export function decode(buf: Uint8Array): DnsPacket;
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {object} QueryOptions
|
|
50
|
+
* @property {DnsQuestion[]} questions
|
|
51
|
+
* @property {DnsRecord[]} [answers] - Known answers for suppression (RFC 6762 §7.1)
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* Encode an mDNS query into one or more packets.
|
|
55
|
+
* If the known-answer list is too large for a single packet, splits across
|
|
56
|
+
* multiple packets per RFC 6762 §7.2: the first packet contains the question
|
|
57
|
+
* and sets the TC bit, continuation packets contain only answers (QDCOUNT=0).
|
|
58
|
+
* @param {QueryOptions} options
|
|
59
|
+
* @returns {Buffer[]} - Array of encoded packets (usually 1)
|
|
60
|
+
*/
|
|
61
|
+
export function encodeQueryPackets({ questions, answers }: QueryOptions): Buffer[];
|
|
62
|
+
/**
|
|
63
|
+
* Encode a single mDNS query packet.
|
|
64
|
+
* @param {QueryOptions} options
|
|
65
|
+
* @returns {Buffer}
|
|
66
|
+
*/
|
|
67
|
+
export function encodeQuery({ questions, answers }: QueryOptions): Buffer;
|
|
68
|
+
export type RecordType = number;
|
|
69
|
+
export namespace RecordType {
|
|
70
|
+
let A: 1;
|
|
71
|
+
let PTR: 12;
|
|
72
|
+
let TXT: 16;
|
|
73
|
+
let AAAA: 28;
|
|
74
|
+
let SRV: 33;
|
|
75
|
+
let ANY: 255;
|
|
76
|
+
}
|
|
77
|
+
export type DnsFlags = {
|
|
78
|
+
/**
|
|
79
|
+
* - true for response, false for query
|
|
80
|
+
*/
|
|
81
|
+
qr: boolean;
|
|
82
|
+
opcode: number;
|
|
83
|
+
/**
|
|
84
|
+
* - Authoritative Answer
|
|
85
|
+
*/
|
|
86
|
+
aa: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* - Truncated
|
|
89
|
+
*/
|
|
90
|
+
tc: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* - Recursion Desired
|
|
93
|
+
*/
|
|
94
|
+
rd: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* - Recursion Available
|
|
97
|
+
*/
|
|
98
|
+
ra: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* - Response code
|
|
101
|
+
*/
|
|
102
|
+
rcode: number;
|
|
103
|
+
};
|
|
104
|
+
export type DnsQuestion = {
|
|
105
|
+
name: string;
|
|
106
|
+
/**
|
|
107
|
+
* - RecordType value
|
|
108
|
+
*/
|
|
109
|
+
type: number;
|
|
110
|
+
/**
|
|
111
|
+
* - QU (unicast-response) bit (RFC 6762 §5.4)
|
|
112
|
+
*/
|
|
113
|
+
qu?: boolean | undefined;
|
|
114
|
+
};
|
|
115
|
+
export type SrvData = {
|
|
116
|
+
priority: number;
|
|
117
|
+
weight: number;
|
|
118
|
+
port: number;
|
|
119
|
+
target: string;
|
|
120
|
+
};
|
|
121
|
+
export type DnsRecord = {
|
|
122
|
+
name: string;
|
|
123
|
+
/**
|
|
124
|
+
* - RecordType value
|
|
125
|
+
*/
|
|
126
|
+
type: number;
|
|
127
|
+
/**
|
|
128
|
+
* - Usually 1 (IN)
|
|
129
|
+
*/
|
|
130
|
+
class: number;
|
|
131
|
+
/**
|
|
132
|
+
* - mDNS cache-flush bit
|
|
133
|
+
*/
|
|
134
|
+
cacheFlush: boolean;
|
|
135
|
+
ttl: number;
|
|
136
|
+
/**
|
|
137
|
+
* - Type-specific data
|
|
138
|
+
*/
|
|
139
|
+
data: string | SrvData | Uint8Array[];
|
|
140
|
+
};
|
|
141
|
+
export type DnsPacket = {
|
|
142
|
+
id: number;
|
|
143
|
+
flags: DnsFlags;
|
|
144
|
+
questions: DnsQuestion[];
|
|
145
|
+
answers: DnsRecord[];
|
|
146
|
+
authorities: DnsRecord[];
|
|
147
|
+
additionals: DnsRecord[];
|
|
148
|
+
};
|
|
149
|
+
export type QueryOptions = {
|
|
150
|
+
questions: DnsQuestion[];
|
|
151
|
+
/**
|
|
152
|
+
* - Known answers for suppression (RFC 6762 §7.1)
|
|
153
|
+
*/
|
|
154
|
+
answers?: DnsRecord[] | undefined;
|
|
155
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('./service.js').Service} Service
|
|
3
|
+
* @typedef {import('./browser.js').BrowseEvent} BrowseEvent
|
|
4
|
+
*/
|
|
5
|
+
export class DnsSdBrowser {
|
|
6
|
+
/**
|
|
7
|
+
* @param {object} [options]
|
|
8
|
+
* @param {number} [options.port=5353] - mDNS port
|
|
9
|
+
* @param {string} [options.interface] - Network interface IP to bind to
|
|
10
|
+
*/
|
|
11
|
+
constructor(options?: {
|
|
12
|
+
port?: number | undefined;
|
|
13
|
+
interface?: string | undefined;
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Start browsing for a specific service type.
|
|
17
|
+
*
|
|
18
|
+
* Returns a ServiceBrowser that is an async iterable yielding BrowseEvents
|
|
19
|
+
* as services appear, disappear, or update on the network.
|
|
20
|
+
*
|
|
21
|
+
* @param {string | { name: string, protocol?: string }} serviceType
|
|
22
|
+
* Service type to browse for. Either a string like "_http._tcp" or
|
|
23
|
+
* an object like `{ name: 'http', protocol: 'tcp' }`.
|
|
24
|
+
* @param {object} [options]
|
|
25
|
+
* @param {AbortSignal} [options.signal] - Signal to cancel browsing
|
|
26
|
+
* @param {string} [options.subtype] - Browse a service subtype (RFC 6763 §7.1).
|
|
27
|
+
* e.g. `browse('_http._tcp', { subtype: '_printer' })` queries
|
|
28
|
+
* `_printer._sub._http._tcp.local`.
|
|
29
|
+
* @param {number} [options.reconfirmTimeoutMs] - Reconfirmation timeout (ms) per RFC 6762 §10.4
|
|
30
|
+
* @param {number} [options.poofTimeoutMs] - POOF window (ms) per RFC 6762 §10.5
|
|
31
|
+
* @param {number} [options.poofResponseWaitMs] - POOF response wait (ms) per RFC 6762 §10.5
|
|
32
|
+
* @returns {ServiceBrowser}
|
|
33
|
+
*/
|
|
34
|
+
browse(serviceType: string | {
|
|
35
|
+
name: string;
|
|
36
|
+
protocol?: string;
|
|
37
|
+
}, options?: {
|
|
38
|
+
signal?: AbortSignal | undefined;
|
|
39
|
+
subtype?: string | undefined;
|
|
40
|
+
reconfirmTimeoutMs?: number | undefined;
|
|
41
|
+
poofTimeoutMs?: number | undefined;
|
|
42
|
+
poofResponseWaitMs?: number | undefined;
|
|
43
|
+
}): ServiceBrowser;
|
|
44
|
+
/**
|
|
45
|
+
* Browse for all service instances on the network, regardless of type.
|
|
46
|
+
*
|
|
47
|
+
* Discovers service types via `_services._dns-sd._udp.local` (RFC 6763 §9),
|
|
48
|
+
* then automatically browses each discovered type for instances. Returns
|
|
49
|
+
* fully resolved BrowseEvents with real host, port, and addresses — the
|
|
50
|
+
* same as `browse()` but across all types.
|
|
51
|
+
*
|
|
52
|
+
* @param {object} [options]
|
|
53
|
+
* @param {AbortSignal} [options.signal]
|
|
54
|
+
* @returns {AllServiceBrowser}
|
|
55
|
+
*/
|
|
56
|
+
browseAll(options?: {
|
|
57
|
+
signal?: AbortSignal | undefined;
|
|
58
|
+
}): AllServiceBrowser;
|
|
59
|
+
/**
|
|
60
|
+
* Browse for service types on the network.
|
|
61
|
+
*
|
|
62
|
+
* Queries `_services._dns-sd._udp.local` (RFC 6763 §9) to discover
|
|
63
|
+
* which service types are being advertised. Returns lightweight
|
|
64
|
+
* Service objects representing types (not instances) — `host`, `port`,
|
|
65
|
+
* and `addresses` will be empty.
|
|
66
|
+
*
|
|
67
|
+
* For discovering fully resolved service instances across all types,
|
|
68
|
+
* use `browseAll()` instead.
|
|
69
|
+
*
|
|
70
|
+
* @param {object} [options]
|
|
71
|
+
* @param {AbortSignal} [options.signal]
|
|
72
|
+
* @returns {ServiceBrowser}
|
|
73
|
+
*/
|
|
74
|
+
browseTypes(options?: {
|
|
75
|
+
signal?: AbortSignal | undefined;
|
|
76
|
+
}): ServiceBrowser;
|
|
77
|
+
/**
|
|
78
|
+
* Re-join multicast groups and restart all browsers.
|
|
79
|
+
*
|
|
80
|
+
* Call this after a network interface change (e.g. WiFi reconnect,
|
|
81
|
+
* Ethernet re-plug). The OS drops multicast group membership when an
|
|
82
|
+
* interface goes down; this method re-establishes it and flushes stale
|
|
83
|
+
* service state.
|
|
84
|
+
*
|
|
85
|
+
* All current services are emitted as `serviceDown` events, and
|
|
86
|
+
* querying restarts with the initial rapid schedule so services on
|
|
87
|
+
* the new network are discovered quickly.
|
|
88
|
+
*/
|
|
89
|
+
rejoin(): void;
|
|
90
|
+
/**
|
|
91
|
+
* Stop all browsers and close the mDNS transport.
|
|
92
|
+
* @returns {Promise<void>}
|
|
93
|
+
*/
|
|
94
|
+
destroy(): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Returns a promise that resolves when the mDNS transport is ready
|
|
97
|
+
* to send and receive packets. Useful for tests or when you need
|
|
98
|
+
* to ensure the socket is bound before external interaction.
|
|
99
|
+
*
|
|
100
|
+
* Note: the transport is started lazily on the first `browse()` or
|
|
101
|
+
* `browseAll()` call. Calling `ready()` before any browse will throw.
|
|
102
|
+
* @returns {Promise<void>}
|
|
103
|
+
*/
|
|
104
|
+
ready(): Promise<void>;
|
|
105
|
+
/** @returns {Promise<void>} */
|
|
106
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
107
|
+
#private;
|
|
108
|
+
}
|
|
109
|
+
export type Service = import("./service.js").Service;
|
|
110
|
+
export type BrowseEvent = import("./browser.js").BrowseEvent;
|
|
111
|
+
import { ServiceBrowser } from './browser.js';
|
|
112
|
+
import { AllServiceBrowser } from './browser.js';
|
|
113
|
+
export { ServiceBrowser, AllServiceBrowser } from "./browser.js";
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service data type for discovered DNS-SD services.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {object} Service
|
|
7
|
+
* @property {string} name - Instance name (e.g. "My Printer")
|
|
8
|
+
* @property {string} type - Service type (e.g. "_http._tcp")
|
|
9
|
+
* @property {string} protocol - Transport protocol ("tcp" or "udp")
|
|
10
|
+
* @property {string} domain - Domain (default "local")
|
|
11
|
+
* @property {string} host - Target hostname (e.g. "printer.local")
|
|
12
|
+
* @property {number} port - Port number
|
|
13
|
+
* @property {string[]} addresses - IPv4 and IPv6 addresses
|
|
14
|
+
* @property {Record<string, string | true>} txt - Parsed TXT key-value pairs
|
|
15
|
+
* @property {Record<string, Uint8Array>} txtRaw - Raw TXT record values
|
|
16
|
+
* @property {string} fqdn - Fully qualified service name
|
|
17
|
+
* @property {string[]} subtypes - Service subtypes
|
|
18
|
+
* @property {number} updatedAt - Timestamp of last update (ms)
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Parse TXT record data (array of Uint8Array strings) into key-value pairs.
|
|
22
|
+
*
|
|
23
|
+
* Per RFC 6763 §6.3:
|
|
24
|
+
* - Format is "key=value" where value is the part after the first '='
|
|
25
|
+
* - Keys without '=' are boolean flags (value is `true`)
|
|
26
|
+
* - Duplicate keys: only the first occurrence is used
|
|
27
|
+
* - Keys are case-insensitive but we preserve original casing
|
|
28
|
+
*
|
|
29
|
+
* @param {Uint8Array[]} txtData
|
|
30
|
+
* @returns {{ txt: Record<string, string | true>, txtRaw: Record<string, Uint8Array> }}
|
|
31
|
+
*/
|
|
32
|
+
export function parseTxtData(txtData: Uint8Array[]): {
|
|
33
|
+
txt: Record<string, string | true>;
|
|
34
|
+
txtRaw: Record<string, Uint8Array>;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Parse a service type string into its components.
|
|
38
|
+
*
|
|
39
|
+
* Accepts either:
|
|
40
|
+
* - Full form: "_http._tcp" or "_http._tcp.local"
|
|
41
|
+
* - Object form: { name: "http", protocol: "tcp" }
|
|
42
|
+
*
|
|
43
|
+
* @param {string | { name: string, protocol?: string }} serviceType
|
|
44
|
+
* @returns {{ type: string, protocol: string, domain: string, queryName: string }}
|
|
45
|
+
*/
|
|
46
|
+
export function parseServiceType(serviceType: string | {
|
|
47
|
+
name: string;
|
|
48
|
+
protocol?: string;
|
|
49
|
+
}): {
|
|
50
|
+
type: string;
|
|
51
|
+
protocol: string;
|
|
52
|
+
domain: string;
|
|
53
|
+
queryName: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Extract the instance name from a fully qualified service name.
|
|
57
|
+
*
|
|
58
|
+
* Given "My Service._http._tcp.local" and type "_http._tcp",
|
|
59
|
+
* returns "My Service".
|
|
60
|
+
*
|
|
61
|
+
* @param {string} fqdn
|
|
62
|
+
* @param {string} serviceType - e.g. "_http._tcp.local"
|
|
63
|
+
* @returns {string}
|
|
64
|
+
*/
|
|
65
|
+
export function extractInstanceName(fqdn: string, serviceType: string): string;
|
|
66
|
+
export type Service = {
|
|
67
|
+
/**
|
|
68
|
+
* - Instance name (e.g. "My Printer")
|
|
69
|
+
*/
|
|
70
|
+
name: string;
|
|
71
|
+
/**
|
|
72
|
+
* - Service type (e.g. "_http._tcp")
|
|
73
|
+
*/
|
|
74
|
+
type: string;
|
|
75
|
+
/**
|
|
76
|
+
* - Transport protocol ("tcp" or "udp")
|
|
77
|
+
*/
|
|
78
|
+
protocol: string;
|
|
79
|
+
/**
|
|
80
|
+
* - Domain (default "local")
|
|
81
|
+
*/
|
|
82
|
+
domain: string;
|
|
83
|
+
/**
|
|
84
|
+
* - Target hostname (e.g. "printer.local")
|
|
85
|
+
*/
|
|
86
|
+
host: string;
|
|
87
|
+
/**
|
|
88
|
+
* - Port number
|
|
89
|
+
*/
|
|
90
|
+
port: number;
|
|
91
|
+
/**
|
|
92
|
+
* - IPv4 and IPv6 addresses
|
|
93
|
+
*/
|
|
94
|
+
addresses: string[];
|
|
95
|
+
/**
|
|
96
|
+
* - Parsed TXT key-value pairs
|
|
97
|
+
*/
|
|
98
|
+
txt: Record<string, string | true>;
|
|
99
|
+
/**
|
|
100
|
+
* - Raw TXT record values
|
|
101
|
+
*/
|
|
102
|
+
txtRaw: Record<string, Uint8Array>;
|
|
103
|
+
/**
|
|
104
|
+
* - Fully qualified service name
|
|
105
|
+
*/
|
|
106
|
+
fqdn: string;
|
|
107
|
+
/**
|
|
108
|
+
* - Service subtypes
|
|
109
|
+
*/
|
|
110
|
+
subtypes: string[];
|
|
111
|
+
/**
|
|
112
|
+
* - Timestamp of last update (ms)
|
|
113
|
+
*/
|
|
114
|
+
updatedAt: number;
|
|
115
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @callback PacketHandler
|
|
3
|
+
* @param {dns.DnsPacket} packet
|
|
4
|
+
* @returns {void}
|
|
5
|
+
*/
|
|
6
|
+
export class MdnsTransport {
|
|
7
|
+
/**
|
|
8
|
+
* @param {object} [options]
|
|
9
|
+
* @param {number} [options.port] - mDNS port (default 5353)
|
|
10
|
+
* @param {string} [options.interface] - Network interface IP to bind to
|
|
11
|
+
*/
|
|
12
|
+
constructor(options?: {
|
|
13
|
+
port?: number | undefined;
|
|
14
|
+
interface?: string | undefined;
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Start the transport: bind sockets and join multicast groups.
|
|
18
|
+
* Always binds IPv4. Attempts IPv6 but does not fail if unavailable.
|
|
19
|
+
* @returns {Promise<void>}
|
|
20
|
+
*/
|
|
21
|
+
start(): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Send an mDNS query on IPv4 (and IPv6 if available).
|
|
24
|
+
* @param {dns.QueryOptions} queryOptions
|
|
25
|
+
* @returns {Promise<void>}
|
|
26
|
+
*/
|
|
27
|
+
sendQuery(queryOptions: dns.QueryOptions): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Register a handler for incoming mDNS response packets.
|
|
30
|
+
* @param {PacketHandler} handler
|
|
31
|
+
*/
|
|
32
|
+
addHandler(handler: PacketHandler): void;
|
|
33
|
+
/**
|
|
34
|
+
* Remove a previously registered handler.
|
|
35
|
+
* @param {PacketHandler} handler
|
|
36
|
+
*/
|
|
37
|
+
removeHandler(handler: PacketHandler): void;
|
|
38
|
+
/**
|
|
39
|
+
* Register a handler for incoming mDNS query packets (QR=0).
|
|
40
|
+
* Used for duplicate question suppression (RFC 6762 §7.3) and
|
|
41
|
+
* Passive Observation of Failures (RFC 6762 §10.5).
|
|
42
|
+
* @param {PacketHandler} handler
|
|
43
|
+
*/
|
|
44
|
+
addQueryHandler(handler: PacketHandler): void;
|
|
45
|
+
/**
|
|
46
|
+
* Remove a previously registered query handler.
|
|
47
|
+
* @param {PacketHandler} handler
|
|
48
|
+
*/
|
|
49
|
+
removeQueryHandler(handler: PacketHandler): void;
|
|
50
|
+
/**
|
|
51
|
+
* Re-join multicast groups on existing sockets.
|
|
52
|
+
*
|
|
53
|
+
* Call this after a network interface change (e.g. WiFi reconnect,
|
|
54
|
+
* Ethernet re-plug). The OS drops multicast group membership when an
|
|
55
|
+
* interface goes down; this method re-establishes it so the socket
|
|
56
|
+
* can receive multicast responses again.
|
|
57
|
+
*/
|
|
58
|
+
rejoinMulticast(): void;
|
|
59
|
+
/**
|
|
60
|
+
* Close all sockets and clean up.
|
|
61
|
+
* @returns {Promise<void>}
|
|
62
|
+
*/
|
|
63
|
+
destroy(): Promise<void>;
|
|
64
|
+
#private;
|
|
65
|
+
}
|
|
66
|
+
export type PacketHandler = (packet: dns.DnsPacket) => void;
|
|
67
|
+
import * as dns from './dns.js';
|