@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
@@ -16,13 +16,13 @@ import org.json.JSONObject
16
16
  * - Never reject for runtime errors; always resolve with { error, errorMessage }.
17
17
  *
18
18
  * Parameters (strict, no legacy):
19
- * - startBroadcast: { type?: string, name?: string, port: number, txt?: Record<string,string> } // txt ignored by Android
19
+ * - startBroadcast: { type?: string, name?: string, port: number, txt?: Record<string,string> }
20
20
  * - stopBroadcast: {}
21
21
  * - discover: { type?: string, name?: string, timeout?: number }
22
22
  *
23
23
  * Return shapes:
24
24
  * - startBroadcast -> { publishing: boolean, name: string, error: boolean, errorMessage: string|null }
25
- * - stopBroadcast -> { publishing: false, error: boolean, errorMessage: string|null }
25
+ * - stopBroadcast -> { publishing: boolean, error: boolean, errorMessage: string|null }
26
26
  * - discover -> {
27
27
  * error: boolean, errorMessage: string|null,
28
28
  * servicesFound: number,
@@ -41,8 +41,7 @@ class mDNSPlugin : Plugin() {
41
41
  }
42
42
 
43
43
  override fun handleOnDestroy() {
44
- mdns.close()
45
- scope.cancel()
44
+ mdns.close { scope.cancel() }
46
45
  }
47
46
 
48
47
  // ----------------------- Helpers -----------------------
@@ -52,22 +51,14 @@ class mDNSPlugin : Plugin() {
52
51
 
53
52
  private fun jnull(msg: String?): Any = msg ?: JSONObject.NULL
54
53
 
55
- private fun normalizeType(raw: String?): String {
56
- val type = raw?.trim()?.takeIf { it.isNotEmpty() } ?: "_http._tcp."
57
- return if (type.endsWith(".")) type else "$type."
58
- }
59
-
60
- private fun normalizeName(raw: String?): String =
61
- raw?.trim()?.takeIf { it.isNotEmpty() } ?: (context?.packageName ?: "DevIOArtsMDNS")
62
-
63
54
  private fun jsResultBroadcast(publishing: Boolean, name: String, error: Boolean, msg: String?): JSObject =
64
55
  JSObject().put("publishing", publishing)
65
56
  .put("name", name)
66
57
  .put("error", error)
67
58
  .put("errorMessage", jnull(msg))
68
59
 
69
- private fun jsResultStop(error: Boolean, msg: String?): JSObject =
70
- JSObject().put("publishing", false)
60
+ private fun jsResultStop(publishing: Boolean, error: Boolean, msg: String?): JSObject =
61
+ JSObject().put("publishing", publishing)
71
62
  .put("error", error)
72
63
  .put("errorMessage", jnull(msg))
73
64
 
@@ -78,6 +69,15 @@ class mDNSPlugin : Plugin() {
78
69
  .put("servicesFound", services.length())
79
70
  .put("services", services)
80
71
 
72
+ private fun serviceObject(s: mDNS.MdnsService): JSObject = JSObject().apply {
73
+ put("name", s.name)
74
+ put("type", s.type)
75
+ put("domain", "local.")
76
+ put("port", s.port)
77
+ put("hosts", JSONArray(s.hosts))
78
+ if (s.txt.isNotEmpty()) put("txt", JSObject().apply { s.txt.forEach { (k, v) -> put(k, v) } })
79
+ }
80
+
81
81
  // ----------------------- API -----------------------
82
82
 
83
83
  @PluginMethod
@@ -88,34 +88,32 @@ class mDNSPlugin : Plugin() {
88
88
  /**
89
89
  * Start advertising a Bonjour/mDNS service.
90
90
  * - type: default "_http._tcp."
91
- * - name: default packageName
91
+ * - name: default DevIOArtsMDNS
92
92
  * - port: required (> 0)
93
93
  */
94
94
  @PluginMethod
95
95
  fun startBroadcast(call: PluginCall) {
96
- val type = normalizeType(call.getString("type"))
97
- val name = normalizeName(call.getString("name"))
98
- val port = call.getInt("port") ?: 0
99
-
100
- if (port !in 1..65535) {
101
- call.resolve(jsResultBroadcast(false, "", true, "Missing/invalid port"))
102
- return
103
- }
104
-
105
- try {
106
- mdns.broadcast(
107
- typeRaw = type,
108
- name = name,
109
- port = port,
110
- onSuccess = { published ->
111
- call.resolve(jsResultBroadcast(true, published, false, null))
112
- },
113
- onError = { err ->
114
- call.resolve(jsResultBroadcast(false, "", true, toErrorMessage(err)))
115
- }
116
- )
117
- } catch (t: Throwable) {
118
- call.resolve(jsResultBroadcast(false, "", true, toErrorMessage(t)))
96
+ scope.launch {
97
+ try {
98
+ val type = MdnsValidation.type(MdnsOptions.string(call.data.opt("type"), "type"))
99
+ val name = MdnsValidation.name(MdnsOptions.string(call.data.opt("name"), "name")).ifEmpty { "DevIOArtsMDNS" }
100
+ val port = MdnsOptions.port(call.data.opt("port"))
101
+ val txt = MdnsOptions.txt(call.data.opt("txt"))
102
+ mdns.broadcast(
103
+ typeRaw = type,
104
+ name = name,
105
+ port = port,
106
+ txt = txt,
107
+ onSuccess = { published ->
108
+ call.resolve(jsResultBroadcast(true, published, false, null))
109
+ },
110
+ onError = { err ->
111
+ call.resolve(jsResultBroadcast(mdns.isPublishing, "", true, toErrorMessage(err)))
112
+ }
113
+ )
114
+ } catch (t: Throwable) {
115
+ call.resolve(jsResultBroadcast(mdns.isPublishing, "", true, toErrorMessage(t)))
116
+ }
119
117
  }
120
118
  }
121
119
 
@@ -125,10 +123,11 @@ class mDNSPlugin : Plugin() {
125
123
  @PluginMethod
126
124
  fun stopBroadcast(call: PluginCall) {
127
125
  try {
128
- mdns.stopBroadcast()
129
- call.resolve(jsResultStop(false, null))
126
+ mdns.stopBroadcast { publishing, error ->
127
+ call.resolve(jsResultStop(publishing, error != null, error?.let { toErrorMessage(it) }))
128
+ }
130
129
  } catch (t: Throwable) {
131
- call.resolve(jsResultStop(true, toErrorMessage(t)))
130
+ call.resolve(jsResultStop(true, true, toErrorMessage(t)))
132
131
  }
133
132
  }
134
133
 
@@ -140,29 +139,53 @@ class mDNSPlugin : Plugin() {
140
139
  */
141
140
  @PluginMethod
142
141
  fun discover(call: PluginCall) {
143
- val type = normalizeType(call.getString("type"))
144
- val targetName = call.getString("name")?.trim()?.takeIf { it.isNotEmpty() }
145
- val timeout = call.getInt("timeout") ?: 3000
146
-
147
142
  scope.launch {
148
143
  try {
144
+ val type = MdnsValidation.type(MdnsOptions.string(call.data.opt("type"), "type"))
145
+ val targetName = MdnsValidation.name(MdnsOptions.string(call.data.opt("name"), "name")).takeIf { it.isNotEmpty() }
146
+ val timeout = MdnsOptions.timeout(call.data.opt("timeout"))
149
147
  val list = mdns.discover(type, targetName, timeout)
150
- val arr = JSONArray(list.map { s ->
151
- JSObject().apply {
152
- put("name", s.name)
153
- put("type", s.type) // Android returns full type with dot
154
- put("domain", "local.") // NSD is mDNS only
155
- val hosts = JSONArray()
156
- s.hosts.forEach { hosts.put(it) }
157
- put("hosts", hosts)
158
- put("port", s.port)
159
- // TXT not available via NSD -> omitted on Android
160
- }
161
- })
148
+ val arr = JSONArray(list.map { serviceObject(it) })
162
149
  call.resolve(jsResultDiscover(false, null, arr))
163
150
  } catch (t: Throwable) {
164
151
  call.resolve(jsResultDiscover(true, toErrorMessage(t), JSONArray()))
165
152
  }
166
153
  }
167
154
  }
155
+
156
+ @PluginMethod
157
+ fun watchServices(call: PluginCall) {
158
+ val types = call.getArray("types")
159
+ val raw = if (types == null) emptyList() else (0 until types.length()).map { types.opt(it) }
160
+ if (raw.isEmpty() || raw.any { it !is String }) {
161
+ call.resolve(JSObject().put("error", true).put("errorMessage", "Expected a nonempty array of service types").put("watchersStarted", 0))
162
+ return
163
+ }
164
+ scope.launch {
165
+ try {
166
+ val started = mdns.watchServices(raw.map { it as String }, object : mDNS.WatchListener {
167
+ override fun onServiceFound(service: mDNS.MdnsService) {
168
+ notifyListeners("serviceFound", JSObject().put("service", serviceObject(service)))
169
+ }
170
+ override fun onServiceLost(service: mDNS.MdnsService) {
171
+ notifyListeners("serviceLost", JSObject().put("service", serviceObject(service)))
172
+ }
173
+ override fun onError(type: String, message: String, watching: Boolean) {
174
+ notifyListeners("watchError", JSObject().put("type", type).put("errorMessage", message).put("watching", watching))
175
+ }
176
+ })
177
+ call.resolve(JSObject().put("error", false).put("errorMessage", JSONObject.NULL).put("watchersStarted", started))
178
+ } catch (t: Throwable) {
179
+ call.resolve(JSObject().put("error", true).put("errorMessage", toErrorMessage(t)).put("watchersStarted", 0))
180
+ }
181
+ }
182
+ }
183
+
184
+ @PluginMethod
185
+ fun stopWatching(call: PluginCall) {
186
+ scope.launch {
187
+ val error = mdns.stopWatching()
188
+ call.resolve(JSObject().put("watching", false).put("error", error != null).put("errorMessage", jnull(error?.message)))
189
+ }
190
+ }
168
191
  }
package/dist/docs.json CHANGED
@@ -101,7 +101,7 @@
101
101
  },
102
102
  {
103
103
  "name": "remarks",
104
- "text": "The result list is normalized across platforms. On Android, `txt` is typically absent."
104
+ "text": "The result list is normalized across platforms."
105
105
  }
106
106
  ],
107
107
  "docs": "Discover services of a given type and optionally filter by instance name.",
@@ -110,6 +110,95 @@
110
110
  "MdnsDiscoverOptions"
111
111
  ],
112
112
  "slug": "discover"
113
+ },
114
+ {
115
+ "name": "watchServices",
116
+ "signature": "(options: MdnsWatchOptions) => Promise<MdnsWatchResult>",
117
+ "parameters": [
118
+ {
119
+ "name": "options",
120
+ "docs": "",
121
+ "type": "MdnsWatchOptions"
122
+ }
123
+ ],
124
+ "returns": "Promise<MdnsWatchResult>",
125
+ "tags": [],
126
+ "docs": "Continuously watch several service types. A valid call replaces the previous watch.\nRegister listeners before starting. Each resolved service emits `serviceFound` once;\ndisappearance emits `serviceLost` with its last snapshot. No loss is emitted for an\nunresolved service. Watching and one-shot discovery can run together.\nThanks to Idrimi for contributing the original continuous discovery feature (PR #7).",
127
+ "complexTypes": [
128
+ "MdnsWatchResult",
129
+ "MdnsWatchOptions"
130
+ ],
131
+ "slug": "watchservices"
132
+ },
133
+ {
134
+ "name": "stopWatching",
135
+ "signature": "() => Promise<MdnsWatchStopResult>",
136
+ "parameters": [],
137
+ "returns": "Promise<MdnsWatchStopResult>",
138
+ "tags": [],
139
+ "docs": "Stop all watchers. No events from that watch are emitted after this resolves.",
140
+ "complexTypes": [
141
+ "MdnsWatchStopResult"
142
+ ],
143
+ "slug": "stopwatching"
144
+ },
145
+ {
146
+ "name": "addListener",
147
+ "signature": "(eventName: 'serviceFound' | 'serviceLost', handler: (event: MdnsServiceEvent) => void) => Promise<PluginListenerHandle>",
148
+ "parameters": [
149
+ {
150
+ "name": "eventName",
151
+ "docs": "",
152
+ "type": "'serviceFound' | 'serviceLost'"
153
+ },
154
+ {
155
+ "name": "handler",
156
+ "docs": "",
157
+ "type": "(event: MdnsServiceEvent) => void"
158
+ }
159
+ ],
160
+ "returns": "Promise<PluginListenerHandle>",
161
+ "tags": [],
162
+ "docs": "",
163
+ "complexTypes": [
164
+ "PluginListenerHandle",
165
+ "MdnsServiceEvent"
166
+ ],
167
+ "slug": "addlistenerservicefound--servicelost-"
168
+ },
169
+ {
170
+ "name": "addListener",
171
+ "signature": "(eventName: 'watchError', handler: (event: MdnsWatchErrorEvent) => void) => Promise<PluginListenerHandle>",
172
+ "parameters": [
173
+ {
174
+ "name": "eventName",
175
+ "docs": "",
176
+ "type": "'watchError'"
177
+ },
178
+ {
179
+ "name": "handler",
180
+ "docs": "",
181
+ "type": "(event: MdnsWatchErrorEvent) => void"
182
+ }
183
+ ],
184
+ "returns": "Promise<PluginListenerHandle>",
185
+ "tags": [],
186
+ "docs": "",
187
+ "complexTypes": [
188
+ "PluginListenerHandle",
189
+ "MdnsWatchErrorEvent"
190
+ ],
191
+ "slug": "addlistenerwatcherror-"
192
+ },
193
+ {
194
+ "name": "removeAllListeners",
195
+ "signature": "() => Promise<void>",
196
+ "parameters": [],
197
+ "returns": "Promise<void>",
198
+ "tags": [],
199
+ "docs": "",
200
+ "complexTypes": [],
201
+ "slug": "removealllisteners"
113
202
  }
114
203
  ],
115
204
  "properties": []
@@ -172,7 +261,7 @@
172
261
  {
173
262
  "name": "publishing",
174
263
  "tags": [],
175
- "docs": "Whether the advertiser is currently active.",
264
+ "docs": "Whether an advertiser is active, or may still be active after failed cleanup.",
176
265
  "complexTypes": [],
177
266
  "type": "boolean"
178
267
  }
@@ -184,7 +273,7 @@
184
273
  "docs": "Options for starting a Bonjour/mDNS advertisement.",
185
274
  "tags": [
186
275
  {
187
- "text": "- The `type` **must** end with a dot (`.`). If omitted, the implementation appends it.\n- Instance `name` should be short and human-readable; the OS may append `\" (n)\"` to ensure uniqueness.\n- Android NSD does not expose TXT records to other apps; advertising TXT is supported on iOS.\n- Native implementations guard publish with a timeout and return `error: true` instead of\n leaving the Promise pending when the OS does not deliver a publish callback.\n- Only one advertisement is active at a time. Starting a new one stops the previous one first.",
276
+ "text": "- Service types accept an optional trailing dot; results always include it.\n- Instance `name` should be short and human-readable; the OS may append `\" (n)\"` to ensure uniqueness.\n Electron rejects a name conflict instead of renaming the service.\n- Advertising TXT is supported on iOS, Android and Electron. Invalid input preserves the active advertisement.\n- Native implementations guard publish with a timeout and return `error: true` instead of\n leaving the Promise pending when the OS does not deliver a publish callback.\n- Only one advertisement is active at a time. Starting a new one stops the previous one first.",
188
277
  "name": "remarks"
189
278
  },
190
279
  {
@@ -212,9 +301,9 @@
212
301
  {
213
302
  "name": "name",
214
303
  "tags": [],
215
- "docs": "Service instance name.",
304
+ "docs": "Service instance name (maximum 63 UTF-8 bytes). Defaults to `DevIOArtsMDNS`.",
216
305
  "complexTypes": [],
217
- "type": "string"
306
+ "type": "string | undefined"
218
307
  },
219
308
  {
220
309
  "name": "port",
@@ -232,7 +321,7 @@
232
321
  "name": "txt",
233
322
  "tags": [
234
323
  {
235
- "text": "May be ignored on Android due to platform limitations.",
324
+ "text": "Keys must be unique ignoring case, nonempty printable ASCII without `=`. Each `key=value` entry must fit in 255 UTF-8 bytes. The complete record is limited to 1300 bytes including length prefixes. Non-string values are rejected.",
236
325
  "name": "remarks"
237
326
  }
238
327
  ],
@@ -272,7 +361,7 @@
272
361
  {
273
362
  "name": "publishing",
274
363
  "tags": [],
275
- "docs": "Whether the advertiser remains active (should be false on success).",
364
+ "docs": "False after confirmed stop; true when stopping failed and the advertiser may still be active.",
276
365
  "complexTypes": [],
277
366
  "type": "boolean"
278
367
  }
@@ -313,7 +402,7 @@
313
402
  {
314
403
  "name": "services",
315
404
  "tags": [],
316
- "docs": "Normalized list of discovered services.",
405
+ "docs": "Resolved services with numeric addresses. Empty on error; partial results are discarded.",
317
406
  "complexTypes": [
318
407
  "MdnsService"
319
408
  ],
@@ -327,7 +416,7 @@
327
416
  "docs": "Normalized description of a discovered Bonjour/mDNS service.\nReturned from {@link mDNSPlugin.discover}.",
328
417
  "tags": [
329
418
  {
330
- "text": "- `hosts` contains resolved IPv4/IPv6 numeric addresses (no DNS names).\n- iOS returns `txt` when present. Android (NSD) does not expose TXT records via public APIs,\n so `txt` is usually omitted on Android.\n- `domain` is typically `\"local.\"`.",
419
+ "text": "- `hosts` contains resolved IPv4/IPv6 numeric addresses (no DNS names).\n- IPv4 addresses precede IPv6; order within each family follows discovery order.\n- Native IPv6 scope suffixes are preserved (`%<scope-id>` on iOS; the OS-provided scope on Android).\n Electron may return link-local IPv6 without a scope; callers must not assume every address is reachable.\n- Resolved TXT records are returned when present.\n- `domain` is typically `\"local.\"`.",
331
420
  "name": "remarks"
332
421
  },
333
422
  {
@@ -360,7 +449,7 @@
360
449
  {
361
450
  "name": "port",
362
451
  "tags": [],
363
- "docs": "TCP port the service advertises.",
452
+ "docs": "Port the service advertises.",
364
453
  "complexTypes": [],
365
454
  "type": "number"
366
455
  },
@@ -372,10 +461,17 @@
372
461
  "name": "example"
373
462
  }
374
463
  ],
375
- "docs": "Resolved numeric IP addresses (IPv4/IPv6).",
464
+ "docs": "Resolved numeric IP addresses, IPv4 first, then IPv6 with any available scope suffix.",
376
465
  "complexTypes": [],
377
466
  "type": "string[]"
378
467
  },
468
+ {
469
+ "name": "hostname",
470
+ "tags": [],
471
+ "docs": "Host name when supplied by the platform, e.g. `\"my-device.local.\"`.",
472
+ "complexTypes": [],
473
+ "type": "string | undefined"
474
+ },
379
475
  {
380
476
  "name": "txt",
381
477
  "tags": [
@@ -384,7 +480,7 @@
384
480
  "name": "remarks"
385
481
  }
386
482
  ],
387
- "docs": "TXT dictionary (key → value). Usually present on iOS; Android NSD does not populate this.",
483
+ "docs": "TXT dictionary (key → value).",
388
484
  "complexTypes": [
389
485
  "MdnsTxt"
390
486
  ],
@@ -398,7 +494,7 @@
398
494
  "docs": "Options for Bonjour/mDNS discovery.",
399
495
  "tags": [
400
496
  {
401
- "text": "- The `name` filter matches the normalized name and also accepts prefix matches\n (to handle OS-added `\" (n)\"` suffixes).\n- When `useNW` is `true` on iOS, discovery uses `NWBrowser` for better P2P behavior.\n- Discovery is timeboxed. Concurrent discovery requests are serialized or safely replace\n the previous native session so old callbacks cannot complete a newer request.",
497
+ "text": "- The `name` filter matches the normalized name and also accepts prefix matches\n (to handle OS-added `\" (n)\"` suffixes).\n- When `useNW` is `true` on iOS, discovery uses `NWBrowser` for better P2P behavior.\n- A valid new discovery replaces the previous request with an error on every platform.\n- Dots and hyphens are equivalent when matching names; returned names retain their advertised spelling.\n- Without a name filter, browsing continues for the entire timeout. With a filter it may finish once a matching IP address resolves.",
402
498
  "name": "remarks"
403
499
  },
404
500
  {
@@ -443,7 +539,7 @@
443
539
  "name": "defaultValue"
444
540
  }
445
541
  ],
446
- "docs": "Discovery timeout in milliseconds.",
542
+ "docs": "Discovery timeout in milliseconds. Finite numbers only; fractions are truncated and the delay is clamped to 0–2147483647.",
447
543
  "complexTypes": [],
448
544
  "type": "number | undefined"
449
545
  },
@@ -460,6 +556,146 @@
460
556
  "type": "boolean | undefined"
461
557
  }
462
558
  ]
559
+ },
560
+ {
561
+ "name": "MdnsWatchResult",
562
+ "slug": "mdnswatchresult",
563
+ "docs": "Native browsers have been requested; later failures are reported through `watchError`.",
564
+ "tags": [],
565
+ "methods": [],
566
+ "properties": [
567
+ {
568
+ "name": "error",
569
+ "tags": [],
570
+ "docs": "",
571
+ "complexTypes": [],
572
+ "type": "boolean"
573
+ },
574
+ {
575
+ "name": "errorMessage",
576
+ "tags": [],
577
+ "docs": "",
578
+ "complexTypes": [],
579
+ "type": "string | null"
580
+ },
581
+ {
582
+ "name": "watchersStarted",
583
+ "tags": [],
584
+ "docs": "",
585
+ "complexTypes": [],
586
+ "type": "number"
587
+ }
588
+ ]
589
+ },
590
+ {
591
+ "name": "MdnsWatchOptions",
592
+ "slug": "mdnswatchoptions",
593
+ "docs": "Options for continuous discovery on iOS, Android and Electron.",
594
+ "tags": [],
595
+ "methods": [],
596
+ "properties": [
597
+ {
598
+ "name": "types",
599
+ "tags": [],
600
+ "docs": "Nonempty list of service types. Trailing dots are optional; duplicates are merged.",
601
+ "complexTypes": [],
602
+ "type": "string[]"
603
+ }
604
+ ]
605
+ },
606
+ {
607
+ "name": "MdnsWatchStopResult",
608
+ "slug": "mdnswatchstopresult",
609
+ "docs": "Stops watching independently of any active broadcast.",
610
+ "tags": [],
611
+ "methods": [],
612
+ "properties": [
613
+ {
614
+ "name": "error",
615
+ "tags": [],
616
+ "docs": "",
617
+ "complexTypes": [],
618
+ "type": "boolean"
619
+ },
620
+ {
621
+ "name": "errorMessage",
622
+ "tags": [],
623
+ "docs": "",
624
+ "complexTypes": [],
625
+ "type": "string | null"
626
+ },
627
+ {
628
+ "name": "watching",
629
+ "tags": [],
630
+ "docs": "",
631
+ "complexTypes": [],
632
+ "type": "boolean"
633
+ }
634
+ ]
635
+ },
636
+ {
637
+ "name": "PluginListenerHandle",
638
+ "slug": "pluginlistenerhandle",
639
+ "docs": "",
640
+ "tags": [],
641
+ "methods": [],
642
+ "properties": [
643
+ {
644
+ "name": "remove",
645
+ "tags": [],
646
+ "docs": "",
647
+ "complexTypes": [],
648
+ "type": "() => Promise<void>"
649
+ }
650
+ ]
651
+ },
652
+ {
653
+ "name": "MdnsServiceEvent",
654
+ "slug": "mdnsserviceevent",
655
+ "docs": "Both found and lost events contain a complete service snapshot, including `hosts`.",
656
+ "tags": [],
657
+ "methods": [],
658
+ "properties": [
659
+ {
660
+ "name": "service",
661
+ "tags": [],
662
+ "docs": "",
663
+ "complexTypes": [
664
+ "MdnsService"
665
+ ],
666
+ "type": "MdnsService"
667
+ }
668
+ ]
669
+ },
670
+ {
671
+ "name": "MdnsWatchErrorEvent",
672
+ "slug": "mdnswatcherrorevent",
673
+ "docs": "Browser failures stop the watch; exhausted service resolution retries leave it running.",
674
+ "tags": [],
675
+ "methods": [],
676
+ "properties": [
677
+ {
678
+ "name": "type",
679
+ "tags": [],
680
+ "docs": "",
681
+ "complexTypes": [],
682
+ "type": "string"
683
+ },
684
+ {
685
+ "name": "errorMessage",
686
+ "tags": [],
687
+ "docs": "",
688
+ "complexTypes": [],
689
+ "type": "string"
690
+ },
691
+ {
692
+ "name": "watching",
693
+ "tags": [],
694
+ "docs": "",
695
+ "complexTypes": [],
696
+ "type": "boolean"
697
+ }
698
+ ]
463
699
  }
464
700
  ],
465
701
  "enums": [],