@devioarts/capacitor-mdns 0.0.2 → 0.0.3
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.
- package/Package.swift +1 -1
- package/android/build.gradle +11 -11
- package/android/src/main/java/com/devioarts/capacitor/mdns/mDNS.kt +93 -24
- package/android/src/main/java/com/devioarts/capacitor/mdns/mDNSPlugin.kt +2 -4
- package/dist/esm/definitions.d.ts +1 -1
- package/electron/mdns-bridge.cjs +1 -2
- package/package.json +16 -19
package/Package.swift
CHANGED
|
@@ -10,7 +10,7 @@ let package = Package(
|
|
|
10
10
|
targets: ["mDNSPlugin"])
|
|
11
11
|
],
|
|
12
12
|
dependencies: [
|
|
13
|
-
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "
|
|
13
|
+
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")
|
|
14
14
|
],
|
|
15
15
|
targets: [
|
|
16
16
|
.target(
|
package/android/build.gradle
CHANGED
|
@@ -11,7 +11,7 @@ buildscript {
|
|
|
11
11
|
mavenCentral()
|
|
12
12
|
}
|
|
13
13
|
dependencies {
|
|
14
|
-
classpath 'com.android.tools.build:gradle:8.
|
|
14
|
+
classpath 'com.android.tools.build:gradle:8.13.2'
|
|
15
15
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24"
|
|
16
16
|
}
|
|
17
17
|
}
|
|
@@ -20,23 +20,23 @@ apply plugin: 'com.android.library'
|
|
|
20
20
|
apply plugin: 'org.jetbrains.kotlin.android'
|
|
21
21
|
|
|
22
22
|
android {
|
|
23
|
-
namespace "com.devioarts.capacitor.mdns"
|
|
24
|
-
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion :
|
|
23
|
+
namespace = "com.devioarts.capacitor.mdns"
|
|
24
|
+
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
|
|
25
25
|
defaultConfig {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
versionCode 1
|
|
29
|
-
versionName "1.0"
|
|
30
|
-
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
26
|
+
minSdk = project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23
|
|
27
|
+
targetSdk = project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
|
|
28
|
+
versionCode = 1
|
|
29
|
+
versionName = "1.0"
|
|
30
|
+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
31
31
|
}
|
|
32
32
|
buildTypes {
|
|
33
33
|
release {
|
|
34
|
-
minifyEnabled false
|
|
34
|
+
minifyEnabled = false
|
|
35
35
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
abortOnError false
|
|
38
|
+
lint {
|
|
39
|
+
abortOnError = false
|
|
40
40
|
}
|
|
41
41
|
compileOptions {
|
|
42
42
|
sourceCompatibility JavaVersion.VERSION_21
|
|
@@ -4,9 +4,11 @@ package com.devioarts.capacitor.mdns
|
|
|
4
4
|
import android.content.Context
|
|
5
5
|
import android.net.nsd.NsdManager
|
|
6
6
|
import android.net.nsd.NsdServiceInfo
|
|
7
|
+
import android.os.Build
|
|
7
8
|
import android.os.Handler
|
|
8
9
|
import android.os.Looper
|
|
9
10
|
import kotlinx.coroutines.*
|
|
11
|
+
import java.util.concurrent.Executor
|
|
10
12
|
import java.util.concurrent.atomic.AtomicReference
|
|
11
13
|
|
|
12
14
|
/**
|
|
@@ -33,7 +35,7 @@ class mDNS(
|
|
|
33
35
|
data class MdnsService(
|
|
34
36
|
val name: String,
|
|
35
37
|
val type: String,
|
|
36
|
-
val
|
|
38
|
+
val hosts: List<String>, // numeric addresses (v4/v6)
|
|
37
39
|
val port: Int
|
|
38
40
|
)
|
|
39
41
|
|
|
@@ -50,6 +52,7 @@ class mDNS(
|
|
|
50
52
|
|
|
51
53
|
/** Main looper utilities for non-suspending entry points. */
|
|
52
54
|
private val mainHandler = Handler(Looper.getMainLooper())
|
|
55
|
+
private val mainExecutor = Executor { command -> mainHandler.post(command) }
|
|
53
56
|
private inline fun runOnMain(crossinline block: () -> Unit) {
|
|
54
57
|
if (Looper.myLooper() === Looper.getMainLooper()) block() else mainHandler.post { block() }
|
|
55
58
|
}
|
|
@@ -130,6 +133,7 @@ class mDNS(
|
|
|
130
133
|
// Prepare state
|
|
131
134
|
discListenerRef.getAndSet(null)?.let { safeStopDiscovery(it) }
|
|
132
135
|
val found = mutableListOf<MdnsService>()
|
|
136
|
+
val serviceInfoCallbacks = mutableListOf<NsdManager.ServiceInfoCallback>()
|
|
133
137
|
val result = CompletableDeferred<List<MdnsService>>()
|
|
134
138
|
|
|
135
139
|
// Normalization for "(n)" suffix appended by the OS.
|
|
@@ -139,12 +143,70 @@ class mDNS(
|
|
|
139
143
|
val t = normalize(targetName ?: return true) // if no target, accept all
|
|
140
144
|
return (c == t) || c.startsWith(t)
|
|
141
145
|
}
|
|
146
|
+
fun upsert(item: MdnsService) {
|
|
147
|
+
val index = found.indexOfFirst { it.name == item.name && it.type == item.type && it.port == item.port }
|
|
148
|
+
if (index >= 0) found[index] = item else found.add(item)
|
|
149
|
+
}
|
|
150
|
+
fun unregisterServiceInfoCallbacks() {
|
|
151
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return
|
|
152
|
+
serviceInfoCallbacks.toList().forEach { safeUnregisterServiceInfoCallback(it) }
|
|
153
|
+
serviceInfoCallbacks.clear()
|
|
154
|
+
}
|
|
155
|
+
fun completeWithCurrentResults(discoveryListener: NsdManager.DiscoveryListener) {
|
|
156
|
+
unregisterServiceInfoCallbacks()
|
|
157
|
+
safeStopDiscovery(discoveryListener)
|
|
158
|
+
if (!result.isCompleted) result.complete(found.toList())
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@Suppress("DEPRECATION")
|
|
162
|
+
fun resolveLegacy(si: NsdServiceInfo, discoveryListener: NsdManager.DiscoveryListener) {
|
|
163
|
+
nsd.resolveService(si, object : NsdManager.ResolveListener {
|
|
164
|
+
override fun onResolveFailed(s: NsdServiceInfo, errorCode: Int) { /* ignore */ }
|
|
165
|
+
|
|
166
|
+
override fun onServiceResolved(s: NsdServiceInfo) {
|
|
167
|
+
upsert(toMdnsService(s))
|
|
168
|
+
|
|
169
|
+
// Early-exit for exact/prefix target match.
|
|
170
|
+
if (targetName != null && matchesTarget(s.serviceName)) {
|
|
171
|
+
completeWithCurrentResults(discoveryListener)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
fun registerServiceInfoCallback(si: NsdServiceInfo, discoveryListener: NsdManager.DiscoveryListener) {
|
|
178
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
179
|
+
resolveLegacy(si, discoveryListener)
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
val callback = object : NsdManager.ServiceInfoCallback {
|
|
184
|
+
override fun onServiceInfoCallbackRegistrationFailed(errorCode: Int) { /* ignore */ }
|
|
185
|
+
override fun onServiceInfoCallbackUnregistered() { /* no-op */ }
|
|
186
|
+
override fun onServiceLost() { /* no-op */ }
|
|
187
|
+
|
|
188
|
+
override fun onServiceUpdated(serviceInfo: NsdServiceInfo) {
|
|
189
|
+
upsert(toMdnsService(serviceInfo))
|
|
190
|
+
|
|
191
|
+
// Early-exit for exact/prefix target match.
|
|
192
|
+
if (targetName != null && matchesTarget(serviceInfo.serviceName)) {
|
|
193
|
+
completeWithCurrentResults(discoveryListener)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
serviceInfoCallbacks.add(callback)
|
|
199
|
+
try {
|
|
200
|
+
nsd.registerServiceInfoCallback(si, mainExecutor, callback)
|
|
201
|
+
} catch (_: Throwable) {
|
|
202
|
+
serviceInfoCallbacks.remove(callback)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
142
205
|
|
|
143
206
|
val listener = object : NsdManager.DiscoveryListener {
|
|
144
207
|
override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
|
|
145
208
|
// Fail fast: stop discovery and complete with whatever we have (likely empty).
|
|
146
|
-
|
|
147
|
-
if (!result.isCompleted) result.complete(found.toList())
|
|
209
|
+
completeWithCurrentResults(this)
|
|
148
210
|
}
|
|
149
211
|
override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) { /* no-op */ }
|
|
150
212
|
override fun onDiscoveryStarted(serviceType: String) { /* no-op */ }
|
|
@@ -154,25 +216,7 @@ class mDNS(
|
|
|
154
216
|
if (targetName != null && !matchesTarget(si.serviceName)) return
|
|
155
217
|
|
|
156
218
|
val discoveryListener = this
|
|
157
|
-
|
|
158
|
-
override fun onResolveFailed(s: NsdServiceInfo, errorCode: Int) { /* ignore */ }
|
|
159
|
-
|
|
160
|
-
override fun onServiceResolved(s: NsdServiceInfo) {
|
|
161
|
-
val item = MdnsService(
|
|
162
|
-
name = s.serviceName,
|
|
163
|
-
type = s.serviceType,
|
|
164
|
-
host = s.host?.hostAddress,
|
|
165
|
-
port = s.port
|
|
166
|
-
)
|
|
167
|
-
found.add(item)
|
|
168
|
-
|
|
169
|
-
// Early-exit for exact/prefix target match.
|
|
170
|
-
if (targetName != null && matchesTarget(s.serviceName)) {
|
|
171
|
-
safeStopDiscovery(discoveryListener)
|
|
172
|
-
if (!result.isCompleted) result.complete(found.toList())
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
})
|
|
219
|
+
registerServiceInfoCallback(si, discoveryListener)
|
|
176
220
|
}
|
|
177
221
|
|
|
178
222
|
override fun onServiceLost(serviceInfo: NsdServiceInfo) { /* no-op */ }
|
|
@@ -184,8 +228,7 @@ class mDNS(
|
|
|
184
228
|
|
|
185
229
|
val timeoutJob = scope.launch {
|
|
186
230
|
delay(timeoutMs.toLong())
|
|
187
|
-
|
|
188
|
-
if (!result.isCompleted) result.complete(found.toList())
|
|
231
|
+
completeWithCurrentResults(listener)
|
|
189
232
|
}
|
|
190
233
|
|
|
191
234
|
try {
|
|
@@ -193,6 +236,7 @@ class mDNS(
|
|
|
193
236
|
} finally {
|
|
194
237
|
// Cleanup in all cases (early-exit or timeout)
|
|
195
238
|
timeoutJob.cancel()
|
|
239
|
+
unregisterServiceInfoCallbacks()
|
|
196
240
|
discListenerRef.getAndSet(null)?.let { safeStopDiscovery(it) }
|
|
197
241
|
}
|
|
198
242
|
}
|
|
@@ -218,4 +262,29 @@ class mDNS(
|
|
|
218
262
|
private fun safeStopDiscovery(l: NsdManager.DiscoveryListener) {
|
|
219
263
|
try { nsd.stopServiceDiscovery(l) } catch (_: Throwable) {}
|
|
220
264
|
}
|
|
265
|
+
|
|
266
|
+
/** Best-effort stop service info updates; NSD may throw if not registered. */
|
|
267
|
+
private fun safeUnregisterServiceInfoCallback(l: NsdManager.ServiceInfoCallback) {
|
|
268
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
269
|
+
try { nsd.unregisterServiceInfoCallback(l) } catch (_: Throwable) {}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private fun toMdnsService(s: NsdServiceInfo): MdnsService =
|
|
274
|
+
MdnsService(
|
|
275
|
+
name = s.serviceName,
|
|
276
|
+
type = s.serviceType,
|
|
277
|
+
hosts = hostAddresses(s),
|
|
278
|
+
port = s.port
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
private fun hostAddresses(s: NsdServiceInfo): List<String> =
|
|
282
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
283
|
+
s.hostAddresses.mapNotNull { it.hostAddress }
|
|
284
|
+
} else {
|
|
285
|
+
legacyHostAddress(s)?.let { listOf(it) } ?: emptyList()
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@Suppress("DEPRECATION")
|
|
289
|
+
private fun legacyHostAddress(s: NsdServiceInfo): String? = s.host?.hostAddress
|
|
221
290
|
}
|
|
@@ -26,7 +26,7 @@ import org.json.JSONObject
|
|
|
26
26
|
* - discover -> {
|
|
27
27
|
* error: boolean, errorMessage: string|null,
|
|
28
28
|
* servicesFound: number,
|
|
29
|
-
* services: Array<{ name, type, domain: "local.", port, hosts
|
|
29
|
+
* services: Array<{ name, type, domain: "local.", port, hosts: string[] }>
|
|
30
30
|
* }
|
|
31
31
|
*/
|
|
32
32
|
@CapacitorPlugin(name = "mDNS")
|
|
@@ -139,10 +139,8 @@ class mDNSPlugin : Plugin() {
|
|
|
139
139
|
put("name", s.name)
|
|
140
140
|
put("type", s.type) // Android returns full type with dot
|
|
141
141
|
put("domain", "local.") // NSD is mDNS only
|
|
142
|
-
// normalize hosts to [] or [addr]
|
|
143
142
|
val hosts = JSONArray()
|
|
144
|
-
|
|
145
|
-
if (!addr.isNullOrEmpty()) hosts.put(addr)
|
|
143
|
+
s.hosts.forEach { hosts.put(it) }
|
|
146
144
|
put("hosts", hosts)
|
|
147
145
|
put("port", s.port)
|
|
148
146
|
// TXT not available via NSD -> omitted on Android
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Values are UTF-8 strings; binary payloads are not supported by this API.
|
|
4
4
|
* @public
|
|
5
5
|
*/
|
|
6
|
-
export
|
|
6
|
+
export type MdnsTxt = Record<string, string>;
|
|
7
7
|
/**
|
|
8
8
|
* Normalized description of a discovered Bonjour/mDNS service.
|
|
9
9
|
* Returned from {@link mDNSPlugin.discover}.
|
package/electron/mdns-bridge.cjs
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
// const { createMDNSAPI } = require('.../electron/mdns-bridge.cjs')
|
|
4
4
|
// contextBridge.exposeInMainWorld('mDNS', createMDNSAPI({ ipcRenderer }))
|
|
5
5
|
// contextBridge.exposeInMainWorld('mdns', createMDNSAPI({ ipcRenderer })) // alias
|
|
6
|
-
// eslint-disable-next-line no-undef
|
|
7
6
|
module.exports.createMDNSAPI = ({ ipcRenderer }) => {
|
|
8
7
|
return {
|
|
9
8
|
/**
|
|
@@ -26,4 +25,4 @@ module.exports.createMDNSAPI = ({ ipcRenderer }) => {
|
|
|
26
25
|
*/
|
|
27
26
|
discover: (options) => ipcRenderer.invoke('mdns:discover', options),
|
|
28
27
|
}
|
|
29
|
-
}
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devioarts/capacitor-mdns",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "mDNS plugin for CapacitorJS",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -57,31 +57,28 @@
|
|
|
57
57
|
"release": "npm publish --access public"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@capacitor/android": "^
|
|
61
|
-
"@capacitor/core": "^
|
|
62
|
-
"@capacitor/docgen": "^0.3.
|
|
63
|
-
"@capacitor/ios": "^
|
|
64
|
-
"@ionic/eslint-config": "^0.4.0",
|
|
60
|
+
"@capacitor/android": "^8.4.0",
|
|
61
|
+
"@capacitor/core": "^8.4.0",
|
|
62
|
+
"@capacitor/docgen": "^0.3.1",
|
|
63
|
+
"@capacitor/ios": "^8.4.0",
|
|
65
64
|
"@ionic/prettier-config": "^4.0.0",
|
|
66
65
|
"@ionic/swiftlint-config": "^2.0.0",
|
|
67
|
-
"eslint": "^8.
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
66
|
+
"@typescript-eslint/eslint-plugin": "^8.61.0",
|
|
67
|
+
"@typescript-eslint/parser": "^8.61.0",
|
|
68
|
+
"bonjour-service": "^1.4.0",
|
|
69
|
+
"electron": "^42.4.0",
|
|
70
|
+
"eslint": "^10.4.1",
|
|
71
|
+
"prettier": "^3.8.4",
|
|
72
|
+
"prettier-plugin-java": "^2.9.7",
|
|
73
|
+
"rimraf": "^6.1.3",
|
|
74
|
+
"rollup": "^4.61.1",
|
|
72
75
|
"swiftlint": "^2.0.0",
|
|
73
|
-
"typescript": "
|
|
74
|
-
"electron": "^37.3.1",
|
|
75
|
-
"bonjour-service": "^1.3.0"
|
|
76
|
+
"typescript": "^6.0.3"
|
|
76
77
|
},
|
|
77
78
|
"peerDependencies": {
|
|
78
|
-
"@capacitor/core": ">=
|
|
79
|
+
"@capacitor/core": ">=8.0.0"
|
|
79
80
|
},
|
|
80
81
|
"prettier": "@ionic/prettier-config",
|
|
81
|
-
"swiftlint": "@ionic/swiftlint-config",
|
|
82
|
-
"eslintConfig": {
|
|
83
|
-
"extends": "@ionic/eslint-config/recommended"
|
|
84
|
-
},
|
|
85
82
|
"capacitor": {
|
|
86
83
|
"ios": {
|
|
87
84
|
"src": "ios"
|