watchServices({ types })is available on iOS, Android and Electron. A nonempty array of valid_name._tcp./_name._udp.types is required. Types are trimmed, trailing dots appended and duplicates merged. Invalid input leaves the current watch running.- One watch group is active. A valid replacement first stops the old group.
watchersStartedcounts requested native browsers; asynchronous startup failures arrive throughwatchError. - Register
serviceFound,serviceLostandwatchErrorlisteners before starting. Found/lost contain{ service }withname, fulltype,domain,port,hostsand optionaltxt/hostname. Found is emitted only after a numeric address resolves.hostsis always an array. Lost uses the last resolved snapshot, and is emitted only if the service was reported as found. A disappearance during resolve cancels that candidate. - Each service emits found once per period of presence. Lost carries the last resolved snapshot; this API does not promise continuous updates or separate events for address or TXT changes.
- A browser / transport failure stops the watch group and emits
{ type, errorMessage, watching: false }. Native resolve errors retry up to three times, then emit the same event withwatching: true; a later discovery can retry that service again. stopWatching()returns{ watching: false, error, errorMessage }, suppresses old callbacks and cancels browsers, pending resolves and retries. Stop failures are reported and cleanup can be retried. It does not stop an advertisement or one-shot discovery. Native manager destruction also closes the watch group.- Android watch groups and one-shot discovery share the pre-API-34 resolver queue and hold separate references to the Wi-Fi multicast lock. Resolved NSD TXT attributes are returned as UTF-8 strings.
- Electron observes Bonjour goodbye packets and PTR TTL expiry. It refreshes browse queries every 30 seconds and checks expiry every second. Detection after abrupt network loss depends on the peer's advertised TTL. Late address packets update the cached snapshot.
- A regular web browser returns
error: truefor watch startup; stop is an idempotent no-op. The manual Electron preload bridge forwards all three event types and supports listener removal.
- All methods resolve with the documented result shape. Runtime failures are reported through
erroranderrorMessageinstead of rejecting when the platform bridge can recover safely. getPluginPlatform()resolves toios,android,electron, orwebfrom the implementation that handled the call.startBroadcast()is guarded by a native timeout on Android, iOS, and Electron. If the OS does not report publish success or failure, the operation is stopped and returned as an error.- Only one advertisement is active at a time. Starting a new advertisement stops the previous one.
- Discovery is timeboxed and cleans up native listeners, browsers, service-info callbacks, and
resolvers after completion. A valid new discovery replaces the previous request with
error: trueand an empty list on every platform. Invalid requests leave the active search running. - Without a name filter, discovery waits for the entire timeout. A matching resolved service with an IP address can finish a filtered search early. A candidate without an address is not returned. Any discovery error discards partial results on every platform.
stopBroadcast()is idempotent and safe to call repeatedly, including after failed or timed-out publish attempts.- Android and Electron wait for stop confirmation. A failed or timed-out stop returns
error: trueandpublishing: truebecause the service may still be active. The registration is retained for cleanup, and a replacement advertisement is not started until stopping succeeds. - On Android versions before T extension 7, mDNS operations hold a reference-counted Wi-Fi multicast lock. Overlapping publishing and discovery keep separate references, so completing discovery does not disable multicast for the active advertisement. Failed publish/stop attempts release their lock reference while retaining any registration needed for late cleanup.
- iOS IPv6 addresses with an interface scope retain it as a
%<scope-id>suffix. - All platforms return IPv4 addresses before IPv6, retaining discovery order within each family.
Electron removes exact duplicate address strings from both discovery and watch snapshots.
Android retains the scope suffix provided by the OS. Electron's Bonjour library may return
link-local IPv6 without an interface scope; sorting does not make those addresses usable on an
IPv6-only network. Try suitable addresses rather than treating
hosts[0]as a reachability guarantee. - On iOS a denied Local Network permission makes
discover()report an error instead of returning an empty list after the timeout. - Android resolves discovered services one at a time before API 34 (a platform limit of
NsdManager.resolveService); on API 34+ resolves run concurrently. - Service types use
_name._tcp.or_name._udp.(optional trailing dot). The service label has 1–15 ASCII letters/digits/hyphens, includes a letter, starts and ends with a letter or digit, and has no consecutive hyphens. Types are trimmed and lowercased; absent or blank types default to_http._tcp.for broadcast/discover. Watch entries must be nonblank. - Broadcast names default to
DevIOArtsMDNSon all platforms, are trimmed, contain no control characters and fit in 63 UTF-8 bytes. Name filters use normalized prefix matching: dots and hyphens compare equally and a final(n)suffix is ignored. Returned names preserve actual advertised spelling. Bonjour on Electron changes dots to hyphens when publishing; native platforms can retain dots. - Ports must be integer numbers from 1 to 65535. Timeout defaults to 3000 ms; nonnumeric or non-finite values are errors, fractions are truncated and delays clamped to 0–2147483647 ms.
- TXT publication works on all three platforms. Values must be strings. Keys must be nonempty printable ASCII without
=, unique ignoring ASCII case. Each UTF-8key=valueentry fits in 255 bytes; the complete TXT record fits in 1300 bytes including the length byte for each entry (the common Android limit). Validation runs before replacing a broadcast. - Android cancels an unconfirmed registration immediately through its original listener; it does not wait indefinitely for a publish confirmation. Failed cancellation is retained for a stop retry.
- Electron reports name conflicts immediately when Bonjour stops its probe; it does not rename the
service. A service stopped during its probe sends no goodbye records, so a rejected duplicate does
not evict the original service from other browsers. Once announcement records have been prepared,
stopping sends goodbye even if the send callback and
upevent have not arrived yet. Stopped services ignore late announcement attempts. This behavior is tested with Bonjour 1.4.0; the supported dependency range is~1.4.0. - Electron creates the Bonjour socket only when the first network operation starts. A failed response
to an incoming query is logged and leaves publication, discovery and watches running. Transport
errorevents remain fatal: they fail pending operations and stop watches. After a fatal transport error, destroy the instance and create a new one. - On a fresh iOS installation, the Local Network prompt may outlast a discovery or publish timeout; the system may also report denial before the user answers. The plugin does not distinguish a pending permission prompt from explicit denial. Explain why access is needed, then allow the user to retry the operation after granting it. This first-run sequence still needs physical-device testing.