@momics/dns-sd-node
v0.1.0
Published
Node.js runtime adapter for @momics/dns-sd-shared: a UDP-multicast DatagramTransport (via node:dgram) driving the shared, standards-compliant mDNS / DNS-SD engine.
Readme
@momics/dns-sd-node
The Node.js runtime package for the @momics/dns-sd family. It supplies a
UDP-multicast DatagramTransport (built on Node's node:dgram) to the
runtime-agnostic @momics/dns-sd-shared mDNS engine and re-exports the
identical public API. No native dependencies, no Rust — Node has everything
needed for raw multicast built in.
- Standards-compliant mDNS / DNS-SD (RFC 6762 + RFC 6763) via the shared engine (probing, conflict resolution, known-answer suppression, TTL cache, goodbyes — all of it).
- Thin adapter: this package is just the socket layer; all protocol logic lives in the shared package and is proven by a common conformance suite.
- Continuous by default:
browsenever times out unless you ask it to.
Install
npm i @momics/dns-sd-nodeNode desktop / server only. This package needs raw UDP multicast sockets and therefore does not work in browsers, and is not intended for mobile (iOS/Android), where the OS owns the mDNS resolver — use a Tauri/native adapter there instead. Node 18+ is required (ships with the built-in test runner and modern
dgram).
Usage
Browse
import { browse } from "@momics/dns-sd-node";
for await (
const svc of browse({ service: { type: "http", protocol: "tcp" } })
) {
// svc.kind is "found" | "resolved" | "updated" | "removed"
console.log(svc.kind, svc.name, svc.addresses, svc.port);
}Stop a browse by break-ing the loop, calling the generator's .return(),
passing an AbortSignal, or setting timeoutMs:
const ac = new AbortController();
setTimeout(() => ac.abort(), 10_000);
for await (
const svc of browse({
service: { type: "ipp", protocol: "tcp" },
signal: ac.signal,
})
) {
console.log(svc);
}Advertise
import { advertise, close } from "@momics/dns-sd-node";
const handle = await advertise({
service: {
type: "http",
protocol: "tcp",
name: "My Web Server",
port: 8080,
txt: { path: "/", version: "1.0" },
},
});
console.log("Advertising as", handle.fullName);
// Later — sends a goodbye so peers drop the service promptly:
await handle.stop();
await close();Managing the transport yourself
The module-level browse / advertise / close wrap a lazily-created default
transport. For finer control (a specific IP family, host name, interface set, or
engine timing), build your own instance:
import { createNodeDnsSd, NodeTransport } from "@momics/dns-sd-node";
// Convenience factory:
const dnsSd = createNodeDnsSd({ family: "IPv4", hostname: "my-device" });
// Or wire the transport into the shared factory directly:
import { createDnsSd } from "@momics/dns-sd-node";
const custom = createDnsSd({ transport: new NodeTransport({ multicastTtl: 4 }) });NodeTransport options:
| Option | Default | Description |
| ------------------- | ------------------ | ------------------------------------------------------------------ |
| family | "IPv4" | "IPv4" (224.0.0.251) or "IPv6" (ff02::fb). |
| hostname | os.hostname() | Advertised host label; a .local suffix is ensured. |
| port | 5353 | mDNS port to bind. |
| multicastTtl | 255 | Outgoing multicast TTL (IPv4). |
| multicastLoopback | true | Loop our multicast back to this host (needed for same-host peers). |
| localAddresses | auto-detected | Our own addresses; [] disables the engine's address-based self-filter. |
| interfaces | all non-internal | Restrict group membership to these interface addresses. |
Networking notes
mDNS uses UDP multicast on port 5353 (group 224.0.0.251 for IPv4,
ff02::fb for IPv6). For discovery to work:
- Firewall: allow inbound/outbound UDP on port
5353. On macOS the built-in firewall usually permits it; on Linux withfirewalld/ufwyou may need to open it (e.g.mdnsservice). Corporate/VPN firewalls frequently block multicast entirely. - Multicast-capable network: many cloud VMs, containers, and Wi-Fi guest networks disable multicast; discovery will silently find nothing there.
- Multiple hosts: peers on different machines are distinguished by source address automatically, so cross-host discovery and advertising work out of the box with the default settings (real interface addresses are advertised in the A/AAAA records).
- Same host, multiple instances: the shared engine ignores datagrams whose
source is one of our own addresses, so by default a browse in one process
won't discover an advertisement made by another process on the same
machine (they share the host's IP). For local-only testing where that is
desired, construct transports with
localAddresses: []: this disables the address-based self-filter (the transport still suppresses each socket's own echoes) at the cost of advertising loopback addresses. This is exactly how the conformance suite runs many nodes on one host.
Testing
# Always-on transport unit tests (no network needed):
npm run test:node --workspace @momics/dns-sd-node
# Also run the shared conformance suite over REAL UDP multicast:
DNS_SD_NETWORK_TESTS=1 npm run test:node --workspace @momics/dns-sd-nodeThe conformance cases come straight from
@momics/dns-sd-shared/testing and are run against real
NodeTransport sockets, proving this runtime behaves identically to every other.
Examples
node packages/dns-sd-node/examples/browse.mjs # browse _http._tcp
node packages/dns-sd-node/examples/advertise.mjs "Demo" 8080License
Dual-licensed under MIT or Apache-2.0, at your option.
