@devioarts/capacitor-mdns 0.1.0 → 0.2.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/DevioartsCapacitorMdns.podspec +3 -3
  3. package/Package.swift +1 -1
  4. package/README.md +221 -37
  5. package/android/build.gradle +3 -9
  6. package/android/src/main/java/com/devioarts/capacitor/mdns/AndroidNsdBackend.kt +145 -0
  7. package/android/src/main/java/com/devioarts/capacitor/mdns/MdnsOptions.kt +32 -0
  8. package/android/src/main/java/com/devioarts/capacitor/mdns/MdnsValidation.kt +26 -0
  9. package/android/src/main/java/com/devioarts/capacitor/mdns/NsdBackend.kt +59 -0
  10. package/android/src/main/java/com/devioarts/capacitor/mdns/mDNS.kt +467 -243
  11. package/android/src/main/java/com/devioarts/capacitor/mdns/mDNSPlugin.kt +80 -57
  12. package/dist/docs.json +250 -14
  13. package/dist/esm/definitions.d.ts +64 -17
  14. package/dist/esm/definitions.js.map +1 -1
  15. package/dist/esm/index.js +22 -2
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/web.d.ts +6 -1
  18. package/dist/esm/web.js +23 -0
  19. package/dist/esm/web.js.map +1 -1
  20. package/dist/plugin.cjs.js +45 -2
  21. package/dist/plugin.cjs.js.map +1 -1
  22. package/dist/plugin.js +45 -2
  23. package/dist/plugin.js.map +1 -1
  24. package/docs/electron.md +29 -3
  25. package/docs/platform-setup.md +6 -0
  26. package/docs/playground.md +33 -2
  27. package/docs/runtime-behavior.md +68 -2
  28. package/electron/dist/plugin-settings.js +10 -3
  29. package/electron/dist/plugin-settings.js.map +1 -1
  30. package/electron/dist/plugin.cjs.js +519 -89
  31. package/electron/dist/plugin.cjs.js.map +1 -1
  32. package/electron/mdns-bridge.cjs +25 -2
  33. package/electron/mdns.ts +514 -81
  34. package/electron/rollup.config.mjs +1 -1
  35. package/electron/src/index.ts +1 -0
  36. package/electron/src/plugin-settings.ts +10 -3
  37. package/electron/types-public/index.d.ts +25 -3
  38. package/ios/Sources/mDNSPlugin/mDNS.swift +407 -117
  39. package/ios/Sources/mDNSPlugin/mDNSPlugin.swift +77 -24
  40. package/ios/Tests/mDNSPluginTests/BridgeTests.swift +46 -0
  41. package/ios/Tests/mDNSPluginTests/MDNSTests.swift +490 -0
  42. package/package.json +11 -4
  43. package/ios/Tests/mDNSPluginTests/mDNSPluginTests.swift +0 -9
@@ -1,11 +1,13 @@
1
1
  # Playground
2
2
 
3
3
  The `playground` directory is the maintained example app for this package.
4
+ Its application identifier is `com.devioarts.pg.cap` on iOS, Android and Electron.
4
5
 
5
6
  It demonstrates:
6
7
 
7
- - starting and stopping an mDNS advertisement;
8
- - discovering services by type and optional name;
8
+ - starting, updating and stopping an mDNS advertisement with JSON TXT records;
9
+ - discovering services by type and optional name, with visible addresses and TXT;
10
+ - watching several service types, with live service lists, found/lost logs and runtime errors;
9
11
  - using the Electron preload bridge;
10
12
  - checking `getPluginPlatform()` so you can confirm which runtime implementation handled a call.
11
13
 
@@ -17,3 +19,32 @@ npm run dev
17
19
  ```
18
20
 
19
21
  For Electron, use the playground together with `devioarts/capacitor-electron`.
22
+
23
+ If native folders are missing after cloning, create the required platform first with `npx cap add ios`, `npx cap add android`, or `npx cap-electron add` inside `playground`.
24
+
25
+ For native builds, build the plugin first from the repository root (`npm run build`), then:
26
+
27
+ ```shell
28
+ cd playground
29
+ npm run build:vite
30
+ npx cap sync
31
+ npm run open-ios # or npm run open-android
32
+ ```
33
+
34
+ On macOS, the sync hook adds the Local Network usage description and the default `_http._tcp`, `_airplay._tcp` and `_googlecast._tcp` Bonjour types to the generated iOS project, preserving any existing description and custom types. Add other types to `NSBonjourServices` before testing them on iOS.
35
+
36
+ For Electron use `npm run build` followed by `npm run open-electron` inside `playground`. The build synchronizes the plugin registry before compiling, including all six methods and the three watch events. The default development origin is `http://localhost:5173`.
37
+ A regular browser displays the UI but cannot perform mDNS; unsupported calls show an error.
38
+
39
+ To test continuous discovery:
40
+
41
+ 1. Publish `_http._tcp.` with TXT on one device. Keep both devices on the same Wi-Fi network.
42
+ 2. On the other device, open Discovery and start watching `_http._tcp., _airplay._tcp., _googlecast._tcp.`.
43
+ 3. Check the live name, type, port, IP addresses and TXT. Publish a second type and check both appear.
44
+ 4. Stop a publisher and check its lost event and removal. Abrupt disappearance can take until its advertised TTL expires.
45
+ 5. Run a one-time search while watching, then stop watching and verify the publisher stays active.
46
+ 6. Stop the watch, change the types and start again. Switching tabs keeps both operations active; use their Stop buttons for independent cleanup.
47
+
48
+ Use dots in the advertised name and hyphens in a name filter to verify equivalent matching.
49
+ Run a long search and click Replace search to test replacement; the older request is logged as an error.
50
+ The Logger panel shows full event snapshots, including the last snapshot on loss.
@@ -1,5 +1,35 @@
1
1
  # Runtime behavior
2
2
 
3
+ ## Continuous discovery
4
+
5
+ - `watchServices({ types })` is available on iOS, Android and Electron. A nonempty array of valid
6
+ `_name._tcp.` / `_name._udp.` types is required. Types are trimmed, trailing dots appended and
7
+ duplicates merged. Invalid input leaves the current watch running.
8
+ - One watch group is active. A valid replacement first stops the old group. `watchersStarted`
9
+ counts requested native browsers; asynchronous startup failures arrive through `watchError`.
10
+ - Register `serviceFound`, `serviceLost` and `watchError` listeners before starting. Found/lost
11
+ contain `{ service }` with `name`, full `type`, `domain`, `port`, `hosts` and optional `txt` /
12
+ `hostname`. Found is emitted only after a numeric address resolves. `hosts` is always an array. Lost uses the last resolved snapshot, and is emitted
13
+ only if the service was reported as found. A disappearance during resolve cancels that candidate.
14
+ - Each service emits found once per period of presence. Lost carries the last resolved snapshot;
15
+ this API does not promise continuous updates or separate events for address or TXT changes.
16
+ - A browser / transport failure stops the watch group and emits `{ type, errorMessage,
17
+ watching: false }`. Native resolve errors retry up to three times, then emit the same event with
18
+ `watching: true`; a later discovery can retry that service again.
19
+ - `stopWatching()` returns `{ watching: false, error, errorMessage }`, suppresses old callbacks and
20
+ cancels browsers, pending resolves and retries. Stop failures are reported and cleanup can be
21
+ retried. It does not stop an advertisement or one-shot discovery. Native manager destruction
22
+ also closes the watch group.
23
+ - Android watch groups and one-shot discovery share the pre-API-34 resolver queue and hold separate
24
+ references to the Wi-Fi multicast lock. Resolved NSD TXT attributes are returned as UTF-8 strings.
25
+ - Electron observes Bonjour goodbye packets and PTR TTL expiry. It refreshes browse queries every
26
+ 30 seconds and checks expiry every second. Detection after abrupt network loss depends on the
27
+ peer's advertised TTL. Late address packets update the cached snapshot.
28
+ - A regular web browser returns `error: true` for watch startup; stop is an idempotent no-op.
29
+ The manual Electron preload bridge forwards all three event types and supports listener removal.
30
+
31
+ ## One-shot discovery and advertising
32
+
3
33
  - All methods resolve with the documented result shape. Runtime failures are reported through
4
34
  `error` and `errorMessage` instead of rejecting when the platform bridge can recover safely.
5
35
  - `getPluginPlatform()` resolves to `ios`, `android`, `electron`, or `web` from the implementation
@@ -8,7 +38,43 @@
8
38
  not report publish success or failure, the operation is stopped and returned as an error.
9
39
  - Only one advertisement is active at a time. Starting a new advertisement stops the previous one.
10
40
  - Discovery is timeboxed and cleans up native listeners, browsers, service-info callbacks, and
11
- resolvers after completion. Overlapping discovery calls are serialized or safely replace the
12
- previous native session so stale callbacks cannot complete a newer request.
41
+ resolvers after completion. A valid new discovery replaces the previous request with `error: true` and an empty list on every platform. Invalid requests leave the active search running.
42
+ - 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.
13
43
  - `stopBroadcast()` is idempotent and safe to call repeatedly, including after failed or timed-out
14
44
  publish attempts.
45
+ - Android and Electron wait for stop confirmation. A failed or timed-out stop returns `error: true`
46
+ and `publishing: true` because the service may still be active. The registration is retained for
47
+ cleanup, and a replacement advertisement is not started until stopping succeeds.
48
+ - On Android versions before T extension 7, mDNS operations hold a reference-counted Wi-Fi
49
+ multicast lock. Overlapping publishing and discovery keep separate references, so completing
50
+ discovery does not disable multicast for the active advertisement. Failed publish/stop attempts
51
+ release their lock reference while retaining any registration needed for late cleanup.
52
+ - iOS IPv6 addresses with an interface scope retain it as a `%<scope-id>` suffix.
53
+ - All platforms return IPv4 addresses before IPv6, retaining discovery order within each family.
54
+ Electron removes exact duplicate address strings from both discovery and watch snapshots.
55
+ Android retains the scope suffix provided by the OS. Electron's Bonjour library may return
56
+ link-local IPv6 without an interface scope; sorting does not make those addresses usable on an
57
+ IPv6-only network. Try suitable addresses rather than treating `hosts[0]` as a reachability guarantee.
58
+ - On iOS a denied Local Network permission makes `discover()` report an error instead of returning an
59
+ empty list after the timeout.
60
+ - Android resolves discovered services one at a time before API 34 (a platform limit of
61
+ `NsdManager.resolveService`); on API 34+ resolves run concurrently.
62
+ - 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.
63
+ - Broadcast names default to `DevIOArtsMDNS` on 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.
64
+ - 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.
65
+ - TXT publication works on all three platforms. Values must be strings. Keys must be nonempty printable ASCII without `=`, unique ignoring ASCII case. Each UTF-8 `key=value` entry 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.
66
+ - 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.
67
+ - Electron reports name conflicts immediately when Bonjour stops its probe; it does not rename the
68
+ service. A service stopped during its probe sends no goodbye records, so a rejected duplicate does
69
+ not evict the original service from other browsers. Once announcement records have been prepared,
70
+ stopping sends goodbye even if the send callback and `up` event have not arrived yet. Stopped
71
+ services ignore late announcement attempts. This behavior is tested with Bonjour 1.4.0;
72
+ the supported dependency range is `~1.4.0`.
73
+ - Electron creates the Bonjour socket only when the first network operation starts. A failed response
74
+ to an incoming query is logged and leaves publication, discovery and watches running. Transport
75
+ `error` events remain fatal: they fail pending operations and stop watches. After a fatal transport
76
+ error, destroy the instance and create a new one.
77
+ - On a fresh iOS installation, the Local Network prompt may outlast a discovery or publish timeout;
78
+ the system may also report denial before the user answers. The plugin does not distinguish a
79
+ pending permission prompt from explicit denial. Explain why access is needed, then allow the user
80
+ to retry the operation after granting it. This first-run sequence still needs physical-device testing.
@@ -1,14 +1,21 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * Metadata consumed by the capacitor-electron runtime generator (`npm run update`).
4
+ * Metadata consumed by the capacitor-electron runtime generator (`npx cap-electron sync`).
5
5
  *
6
6
  * Import path for the JS layer: `@devioarts/capacitor-mdns/electron/settings`.
7
7
  */
8
8
  const pluginSettings = {
9
9
  pluginClass: 'mDNS',
10
- pluginMethods: ['getPluginPlatform', 'startBroadcast', 'stopBroadcast', 'discover'],
11
- pluginEvents: [],
10
+ pluginMethods: [
11
+ 'getPluginPlatform',
12
+ 'startBroadcast',
13
+ 'stopBroadcast',
14
+ 'discover',
15
+ 'watchServices',
16
+ 'stopWatching',
17
+ ],
18
+ pluginEvents: ['serviceFound', 'serviceLost', 'watchError'],
12
19
  };
13
20
 
14
21
  exports.pluginSettings = pluginSettings;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-settings.js","sources":["../build/electron/src/plugin-settings.js"],"sourcesContent":["/**\n * Metadata consumed by the capacitor-electron runtime generator (`npm run update`).\n *\n * Import path for the JS layer: `@devioarts/capacitor-mdns/electron/settings`.\n */\nexport const pluginSettings = {\n pluginClass: 'mDNS',\n pluginMethods: ['getPluginPlatform', 'startBroadcast', 'stopBroadcast', 'discover'],\n pluginEvents: [],\n};\n//# sourceMappingURL=plugin-settings.js.map"],"names":[],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACY,MAAC,cAAc,GAAG;AAC9B,IAAI,WAAW,EAAE,MAAM;AACvB,IAAI,aAAa,EAAE,CAAC,mBAAmB,EAAE,gBAAgB,EAAE,eAAe,EAAE,UAAU,CAAC;AACvF,IAAI,YAAY,EAAE,EAAE;AACpB;;;;"}
1
+ {"version":3,"file":"plugin-settings.js","sources":["../build/electron/src/plugin-settings.js"],"sourcesContent":["/**\n * Metadata consumed by the capacitor-electron runtime generator (`npx cap-electron sync`).\n *\n * Import path for the JS layer: `@devioarts/capacitor-mdns/electron/settings`.\n */\nexport const pluginSettings = {\n pluginClass: 'mDNS',\n pluginMethods: [\n 'getPluginPlatform',\n 'startBroadcast',\n 'stopBroadcast',\n 'discover',\n 'watchServices',\n 'stopWatching',\n ],\n pluginEvents: ['serviceFound', 'serviceLost', 'watchError'],\n};\n//# sourceMappingURL=plugin-settings.js.map"],"names":[],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACY,MAAC,cAAc,GAAG;AAC9B,IAAI,WAAW,EAAE,MAAM;AACvB,IAAI,aAAa,EAAE;AACnB,QAAQ,mBAAmB;AAC3B,QAAQ,gBAAgB;AACxB,QAAQ,eAAe;AACvB,QAAQ,UAAU;AAClB,QAAQ,eAAe;AACvB,QAAQ,cAAc;AACtB,KAAK;AACL,IAAI,YAAY,EAAE,CAAC,cAAc,EAAE,aAAa,EAAE,YAAY,CAAC;AAC/D;;;;"}