@devioarts/capacitor-mdns 0.0.3 → 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 (46) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/DevioartsCapacitorMdns.podspec +3 -3
  3. package/Package.swift +1 -1
  4. package/README.md +272 -101
  5. package/android/build.gradle +3 -9
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/com/devioarts/capacitor/mdns/AndroidNsdBackend.kt +145 -0
  8. package/android/src/main/java/com/devioarts/capacitor/mdns/MdnsOptions.kt +32 -0
  9. package/android/src/main/java/com/devioarts/capacitor/mdns/MdnsValidation.kt +26 -0
  10. package/android/src/main/java/com/devioarts/capacitor/mdns/NsdBackend.kt +59 -0
  11. package/android/src/main/java/com/devioarts/capacitor/mdns/mDNS.kt +496 -175
  12. package/android/src/main/java/com/devioarts/capacitor/mdns/mDNSPlugin.kt +85 -49
  13. package/dist/docs.json +313 -15
  14. package/dist/esm/definitions.d.ts +89 -16
  15. package/dist/esm/definitions.js.map +1 -1
  16. package/dist/esm/index.js +22 -1
  17. package/dist/esm/index.js.map +1 -1
  18. package/dist/esm/web.d.ts +8 -1
  19. package/dist/esm/web.js +35 -3
  20. package/dist/esm/web.js.map +1 -1
  21. package/dist/plugin.cjs.js +57 -4
  22. package/dist/plugin.cjs.js.map +1 -1
  23. package/dist/plugin.js +57 -4
  24. package/dist/plugin.js.map +1 -1
  25. package/docs/electron.md +91 -0
  26. package/docs/platform-setup.md +58 -0
  27. package/docs/playground.md +50 -0
  28. package/docs/runtime-behavior.md +80 -0
  29. package/electron/dist/plugin-settings.js +22 -0
  30. package/electron/dist/plugin-settings.js.map +1 -0
  31. package/electron/dist/plugin.cjs.js +774 -0
  32. package/electron/dist/plugin.cjs.js.map +1 -0
  33. package/electron/global.d.ts +2 -0
  34. package/electron/mdns-bridge.cjs +33 -4
  35. package/electron/mdns.ts +628 -107
  36. package/electron/rollup.config.mjs +22 -0
  37. package/electron/src/index.ts +2 -0
  38. package/electron/src/plugin-settings.ts +19 -0
  39. package/electron/tsconfig.json +21 -0
  40. package/electron/types-public/index.d.ts +47 -0
  41. package/ios/Sources/mDNSPlugin/mDNS.swift +481 -124
  42. package/ios/Sources/mDNSPlugin/mDNSPlugin.swift +85 -23
  43. package/ios/Tests/mDNSPluginTests/BridgeTests.swift +46 -0
  44. package/ios/Tests/mDNSPluginTests/MDNSTests.swift +490 -0
  45. package/package.json +55 -20
  46. package/ios/Tests/mDNSPluginTests/mDNSPluginTests.swift +0 -15
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst mDNS = registerPlugin('mDNS', {\n web: () => import('./web').then((m) => new m.mDNSWeb()),\n});\nexport * from './definitions';\nexport { mDNS };\n//# sourceMappingURL=index.js.map","// src/web.ts\nimport { WebPlugin } from '@capacitor/core';\n/**\n * Web implementation of the mDNS plugin.\n *\n * This class behaves as a thin proxy:\n * - In Electron (when a preload exposes window.mDNS/window.mdns), calls are forwarded over IPC.\n * - In a regular browser, mDNS is not available; methods resolve with well-shaped stub values\n * and log a console message with the [WEB_NOT_SUPPORTED] tag.\n */\nexport class mDNSWeb extends WebPlugin {\n /** Electron preload bridge (if present). */\n get electronApi() {\n if (typeof window === 'undefined')\n return undefined;\n return window.mDNS || window.mdns;\n }\n async startBroadcast(options) {\n const api = this.electronApi;\n if (api === null || api === void 0 ? void 0 : api.startBroadcast)\n return api.startBroadcast(options);\n console.log('[WEB_NOT_SUPPORTED] startBroadcast', options);\n // Keep the shape consistent even when not supported\n return { publishing: true, name: '', error: false, errorMessage: null };\n }\n async stopBroadcast() {\n const api = this.electronApi;\n if (api === null || api === void 0 ? void 0 : api.stopBroadcast)\n return api.stopBroadcast();\n console.log('[WEB_NOT_SUPPORTED] stopBroadcast');\n return { publishing: false, error: false, errorMessage: null };\n }\n async discover(options = {}) {\n const api = this.electronApi;\n if (api === null || api === void 0 ? void 0 : api.discover)\n return api.discover(options);\n console.log('[WEB_NOT_SUPPORTED] discover', options);\n return { services: [], error: false, errorMessage: null, servicesFound: 0 };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,IAAI,GAAGA,mBAAc,CAAC,MAAM,EAAE;IACpC,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;IAC3D,CAAC;;ICHD;IAEA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACO,MAAM,OAAO,SAASC,cAAS,CAAC;IACvC;IACA,IAAI,IAAI,WAAW,GAAG;IACtB,QAAQ,IAAI,OAAO,MAAM,KAAK,WAAW;IACzC,YAAY,OAAO,SAAS;IAC5B,QAAQ,OAAO,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI;IACzC,IAAI;IACJ,IAAI,MAAM,cAAc,CAAC,OAAO,EAAE;IAClC,QAAQ,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW;IACpC,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,MAAM,GAAG,MAAM,GAAG,GAAG,CAAC,cAAc;IACxE,YAAY,OAAO,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC;IAC9C,QAAQ,OAAO,CAAC,GAAG,CAAC,oCAAoC,EAAE,OAAO,CAAC;IAClE;IACA,QAAQ,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE;IAC/E,IAAI;IACJ,IAAI,MAAM,aAAa,GAAG;IAC1B,QAAQ,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW;IACpC,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,MAAM,GAAG,MAAM,GAAG,GAAG,CAAC,aAAa;IACvE,YAAY,OAAO,GAAG,CAAC,aAAa,EAAE;IACtC,QAAQ,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC;IACxD,QAAQ,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE;IACtE,IAAI;IACJ,IAAI,MAAM,QAAQ,CAAC,OAAO,GAAG,EAAE,EAAE;IACjC,QAAQ,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW;IACpC,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,MAAM,GAAG,MAAM,GAAG,GAAG,CAAC,QAAQ;IAClE,YAAY,OAAO,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC;IACxC,QAAQ,OAAO,CAAC,GAAG,CAAC,8BAA8B,EAAE,OAAO,CAAC;IAC5D,QAAQ,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,EAAE;IACnF,IAAI;IACJ;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst platformPlugin = registerPlugin('mDNS', {\n web: () => import('./web').then((m) => new m.mDNSWeb()),\n electron: () => {\n var _a, _b;\n const plugin = (_b = (_a = window.CapacitorCustomPlatform) === null || _a === void 0 ? void 0 : _a.plugins) === null || _b === void 0 ? void 0 : _b.mDNS;\n if (!plugin)\n throw new Error('mDNS Electron plugin is not registered; run npx cap-electron sync in the app');\n return Promise.resolve(plugin);\n },\n});\nconst mDNS = new Proxy(platformPlugin, {\n get(target, property, receiver) {\n if (property !== 'discover')\n return Reflect.get(target, property, receiver);\n return (options) => {\n const timeout = options === null || options === void 0 ? void 0 : options.timeout;\n // Native JSON bridges turn NaN/Infinity into null, losing the invalid input.\n if (timeout != null && (typeof timeout !== 'number' || !Number.isFinite(timeout))) {\n return Promise.resolve({ error: true, errorMessage: 'Invalid timeout', servicesFound: 0, services: [] });\n }\n return target.discover(options);\n };\n },\n});\nexport * from './definitions';\nexport { mDNS };\n//# sourceMappingURL=index.js.map","// src/web.ts\nimport { WebPlugin } from '@capacitor/core';\n/**\n * Web implementation of the mDNS plugin.\n *\n * This class behaves as a thin proxy:\n * - In Electron (when a preload exposes window.mDNS/window.mdns), calls are forwarded over IPC.\n * - In a regular browser, mDNS is not available; methods resolve with well-shaped stub values\n * and log a console message with the [WEB_NOT_SUPPORTED] tag.\n */\nexport class mDNSWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.unsupportedMessage = 'mDNS is not supported in this browser runtime';\n }\n /** Electron preload bridge (if present). */\n get electronApi() {\n if (typeof window === 'undefined')\n return undefined;\n return window.mDNS || window.mdns;\n }\n async getPluginPlatform() {\n const api = this.electronApi;\n if (api === null || api === void 0 ? void 0 : api.getPluginPlatform)\n return api.getPluginPlatform();\n return { platform: 'web' };\n }\n async startBroadcast(options) {\n const api = this.electronApi;\n if (api === null || api === void 0 ? void 0 : api.startBroadcast)\n return api.startBroadcast(options);\n console.log('[WEB_NOT_SUPPORTED] startBroadcast', options);\n return { publishing: false, name: '', error: true, errorMessage: this.unsupportedMessage };\n }\n async stopBroadcast() {\n const api = this.electronApi;\n if (api === null || api === void 0 ? void 0 : api.stopBroadcast)\n return api.stopBroadcast();\n console.log('[WEB_NOT_SUPPORTED] stopBroadcast');\n return { publishing: false, error: false, errorMessage: null };\n }\n async discover(options = {}) {\n const api = this.electronApi;\n if (api === null || api === void 0 ? void 0 : api.discover)\n return api.discover(options);\n console.log('[WEB_NOT_SUPPORTED] discover', options);\n return { services: [], error: true, errorMessage: this.unsupportedMessage, servicesFound: 0 };\n }\n async watchServices(options) {\n var _a;\n if ((_a = this.electronApi) === null || _a === void 0 ? void 0 : _a.watchServices)\n return this.electronApi.watchServices(options);\n return { error: true, errorMessage: this.unsupportedMessage, watchersStarted: 0 };\n }\n async stopWatching() {\n var _a;\n if ((_a = this.electronApi) === null || _a === void 0 ? void 0 : _a.stopWatching)\n return this.electronApi.stopWatching();\n return { watching: false, error: false, errorMessage: null };\n }\n async addListener(eventName, handler) {\n var _a;\n if ((_a = this.electronApi) === null || _a === void 0 ? void 0 : _a.addListener)\n return this.electronApi.addListener(eventName, handler);\n return super.addListener(eventName, handler);\n }\n async removeAllListeners() {\n var _a, _b;\n await ((_b = (_a = this.electronApi) === null || _a === void 0 ? void 0 : _a.removeAllListeners) === null || _b === void 0 ? void 0 : _b.call(_a));\n await super.removeAllListeners();\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ # Electron setup
2
+
3
+ Electron support is maintained for
4
+ [`devioarts/capacitor-electron`](https://github.com/devioarts/capacitor-electron).
5
+
6
+ ## Capacitor Electron
7
+
8
+ Use the root package API in application code, the same as iOS and Android:
9
+
10
+ ```ts
11
+ import { mDNS } from '@devioarts/capacitor-mdns';
12
+
13
+ await mDNS.startBroadcast({ type: '_http._tcp.', name: 'MyService', port: 9100 });
14
+ const platform = await mDNS.getPluginPlatform();
15
+ ```
16
+
17
+ The Electron runtime generator reads plugin metadata from:
18
+
19
+ ```ts
20
+ import { pluginSettings } from '@devioarts/capacitor-mdns/electron/settings';
21
+ ```
22
+
23
+ After installing or updating the plugin, run `npx cap-electron sync` in the application. If the
24
+ generated runtime has no `mDNS` plugin, the root API rejects with an error identifying this step.
25
+ This is a runtime setup error, before a platform implementation can return an operation result.
26
+
27
+ The Electron main-process plugin class is exported from:
28
+
29
+ ```ts
30
+ import { mDNS } from '@devioarts/capacitor-mdns/electron';
31
+ ```
32
+
33
+ ## Manual bridge
34
+
35
+ Continuous discovery uses the same `watchServices`, `stopWatching` and `addListener` API as native
36
+ platforms. After updating the plugin, run `npx cap-electron sync` so the generated metadata includes
37
+ the new methods and the `serviceFound`, `serviceLost`, `watchError` events.
38
+
39
+ The older manual bridge remains available for apps that do not use
40
+ `devioarts/capacitor-electron`.
41
+
42
+ The Electron runtime needs `bonjour-service`. It is declared as an optional peer dependency
43
+ (only Electron apps need it), so install it in the Electron app:
44
+
45
+ ```shell
46
+ npm i bonjour-service@~1.4.0
47
+ ```
48
+
49
+ The supported range is `~1.4.0` because probe cleanup uses Bonjour 1.4 service behavior. Before widening
50
+ it, run the real-registry regressions for conflicts, pending announcements and goodbye packets.
51
+
52
+ ### Main process
53
+
54
+ ```ts
55
+ import { app } from 'electron';
56
+ import { mDNS } from '@devioarts/capacitor-mdns/electron';
57
+
58
+ const mdns = new mDNS(undefined, {
59
+ // Only needed for an exact development server or a custom application origin:
60
+ // trustedOrigins: ['http://localhost:5173'],
61
+ });
62
+
63
+ app.whenReady().then(() => {
64
+ mdns.init();
65
+ });
66
+
67
+ app.on('before-quit', async () => {
68
+ await mdns.destroy();
69
+ });
70
+ ```
71
+
72
+ ### Preload
73
+
74
+ ```js
75
+ const { contextBridge, ipcRenderer } = require('electron');
76
+ const { createMDNSAPI } = require('@devioarts/capacitor-mdns/electron/mdns-bridge.cjs');
77
+
78
+ contextBridge.exposeInMainWorld('mDNS', createMDNSAPI({ ipcRenderer }));
79
+ contextBridge.exposeInMainWorld('mdns', createMDNSAPI({ ipcRenderer }));
80
+ ```
81
+
82
+ ### IPC trust
83
+
84
+ Manual handlers accept calls only from a trusted top-level frame. By default these are
85
+ `capacitor-electron://localhost` and `file:` documents inside `app.getAppPath()`.
86
+ Unpackaged apps also accept the Capacitor Electron default dev origin `http://localhost:5173`;
87
+ that default dev origin is disabled in packaged apps.
88
+ For a custom protocol or development server, configure its exact origin with `trustedOrigins`.
89
+ Third-party pages, child frames and other origins are rejected. Event delivery uses the same
90
+ URL allowlist, so windows displaying remote pages do not receive service data. The generated
91
+ Capacitor Electron route additionally applies its runtime's sender validation.
@@ -0,0 +1,58 @@
1
+ # Platform setup
2
+
3
+ ## Android
4
+
5
+ Add the mDNS/network permissions to your Android manifest.
6
+
7
+ `android/app/src/main/AndroidManifest.xml`
8
+
9
+ ```xml
10
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
11
+ <uses-permission android:name="android.permission.INTERNET" />
12
+ <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
13
+
14
+ <application>
15
+ <!-- Your app configuration -->
16
+ </application>
17
+ </manifest>
18
+ ```
19
+
20
+ The plugin also declares these permissions in its own Android manifest, so they
21
+ are merged into your app during `npx cap sync`. Keeping them in your app
22
+ manifest is still useful when you want the native project to show the network
23
+ requirements explicitly.
24
+
25
+ On some devices, mDNS discovery can still return no results if multicast traffic is blocked by the
26
+ network, router, VPN, or device policy.
27
+
28
+ The plugin does not request location or `NEARBY_WIFI_DEVICES` by default. Add
29
+ those only if your app has separate Wi-Fi scanning or nearby-device features
30
+ that require them.
31
+
32
+ ## iOS
33
+
34
+ Add the local network permissions and every Bonjour service type your app uses to
35
+ `ios/App/App/Info.plist`.
36
+
37
+ ```xml
38
+ <key>NSLocalNetworkUsageDescription</key>
39
+ <string>It is needed for the correct functioning of the application</string>
40
+ <key>NSAppTransportSecurity</key>
41
+ <dict>
42
+ <key>NSAllowsLocalNetworking</key>
43
+ <true/>
44
+ </dict>
45
+ <key>NSBonjourServices</key>
46
+ <array>
47
+ <string>_http._tcp</string>
48
+ <string>_airplay._tcp</string>
49
+ <string>_googlecast._tcp</string>
50
+ </array>
51
+ ```
52
+
53
+ For a custom service such as `_capmdnse2e._tcp.`, add `_capmdnse2e._tcp` to
54
+ `NSBonjourServices`.
55
+
56
+ For continuous watching, include every type passed to `watchServices`. For example, watching
57
+ `['_http._tcp.', '_airplay._tcp.', '_googlecast._tcp.']` requires all three entries shown above. A denied
58
+ Local Network permission is reported through `watchError` and stops the watch group.
@@ -0,0 +1,50 @@
1
+ # Playground
2
+
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.
5
+
6
+ It demonstrates:
7
+
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;
11
+ - using the Electron preload bridge;
12
+ - checking `getPluginPlatform()` so you can confirm which runtime implementation handled a call.
13
+
14
+ Run it from the playground directory:
15
+
16
+ ```shell
17
+ npm install
18
+ npm run dev
19
+ ```
20
+
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.
@@ -0,0 +1,80 @@
1
+ # Runtime behavior
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
+
33
+ - All methods resolve with the documented result shape. Runtime failures are reported through
34
+ `error` and `errorMessage` instead of rejecting when the platform bridge can recover safely.
35
+ - `getPluginPlatform()` resolves to `ios`, `android`, `electron`, or `web` from the implementation
36
+ that handled the call.
37
+ - `startBroadcast()` is guarded by a native timeout on Android, iOS, and Electron. If the OS does
38
+ not report publish success or failure, the operation is stopped and returned as an error.
39
+ - Only one advertisement is active at a time. Starting a new advertisement stops the previous one.
40
+ - Discovery is timeboxed and cleans up native listeners, browsers, service-info callbacks, and
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.
43
+ - `stopBroadcast()` is idempotent and safe to call repeatedly, including after failed or timed-out
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.
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Metadata consumed by the capacitor-electron runtime generator (`npx cap-electron sync`).
5
+ *
6
+ * Import path for the JS layer: `@devioarts/capacitor-mdns/electron/settings`.
7
+ */
8
+ const pluginSettings = {
9
+ pluginClass: 'mDNS',
10
+ pluginMethods: [
11
+ 'getPluginPlatform',
12
+ 'startBroadcast',
13
+ 'stopBroadcast',
14
+ 'discover',
15
+ 'watchServices',
16
+ 'stopWatching',
17
+ ],
18
+ pluginEvents: ['serviceFound', 'serviceLost', 'watchError'],
19
+ };
20
+
21
+ exports.pluginSettings = pluginSettings;
22
+ //# sourceMappingURL=plugin-settings.js.map
@@ -0,0 +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 (`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;;;;"}