capacitor-mdns-discovery 0.0.2
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/CapacitorMdnsDiscovery.podspec +17 -0
- package/Package.swift +28 -0
- package/README.md +201 -0
- package/android/build.gradle +60 -0
- package/android/src/main/AndroidManifest.xml +9 -0
- package/android/src/main/java/com/rajendrakhope/plugins/mdns/MdnsDiscoveryPlugin.kt +203 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +238 -0
- package/dist/esm/definitions.d.ts +30 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +6 -0
- package/dist/esm/web.js +10 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +24 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +27 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/MdnsDiscoveryPlugin/MdnsDiscovery.swift +98 -0
- package/ios/Sources/MdnsDiscoveryPlugin/MdnsDiscoveryPlugin.swift +47 -0
- package/ios/Tests/MdnsDiscoveryPluginTests/MdnsDiscoveryTests.swift +12 -0
- package/package.json +80 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'CapacitorMdnsDiscovery'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.license = package['license']
|
|
10
|
+
s.homepage = package['repository']['url']
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
|
|
13
|
+
s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
|
|
14
|
+
s.ios.deployment_target = '15.0'
|
|
15
|
+
s.dependency 'Capacitor'
|
|
16
|
+
s.swift_version = '5.1'
|
|
17
|
+
end
|
package/Package.swift
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// swift-tools-version: 5.9
|
|
2
|
+
import PackageDescription
|
|
3
|
+
|
|
4
|
+
let package = Package(
|
|
5
|
+
name: "CapacitorMdnsDiscovery",
|
|
6
|
+
platforms: [.iOS(.v15)],
|
|
7
|
+
products: [
|
|
8
|
+
.library(
|
|
9
|
+
name: "CapacitorMdnsDiscovery",
|
|
10
|
+
targets: ["MdnsDiscoveryPlugin"])
|
|
11
|
+
],
|
|
12
|
+
dependencies: [
|
|
13
|
+
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")
|
|
14
|
+
],
|
|
15
|
+
targets: [
|
|
16
|
+
.target(
|
|
17
|
+
name: "MdnsDiscoveryPlugin",
|
|
18
|
+
dependencies: [
|
|
19
|
+
.product(name: "Capacitor", package: "capacitor-swift-pm"),
|
|
20
|
+
.product(name: "Cordova", package: "capacitor-swift-pm")
|
|
21
|
+
],
|
|
22
|
+
path: "ios/Sources/MdnsDiscoveryPlugin"),
|
|
23
|
+
.testTarget(
|
|
24
|
+
name: "MdnsDiscoveryPluginTests",
|
|
25
|
+
dependencies: ["MdnsDiscoveryPlugin"],
|
|
26
|
+
path: "ios/Tests/MdnsDiscoveryPluginTests")
|
|
27
|
+
]
|
|
28
|
+
)
|
package/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# capacitor-mdns-discovery
|
|
2
|
+
|
|
3
|
+
mDNS device discovery for IoT Devices
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install capacitor-mdns-discovery
|
|
9
|
+
npx cap sync
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
### iOS
|
|
13
|
+
|
|
14
|
+
For iOS, make sure you enable local network and Bonjour service discovery in your app’s `Info.plist`:
|
|
15
|
+
|
|
16
|
+
```xml
|
|
17
|
+
<key>NSLocalNetworkUsageDescription</key>
|
|
18
|
+
<string>Discover devices on the local network.</string>
|
|
19
|
+
<key>NSBonjourServices</key>
|
|
20
|
+
<array>
|
|
21
|
+
<string>_http._tcp.</string>
|
|
22
|
+
<!-- Add any additional service types you intend to discover -->
|
|
23
|
+
</array>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Android
|
|
27
|
+
|
|
28
|
+
No additional configuration is required beyond the standard Capacitor Android setup. Ensure your app includes network permissions (such as `INTERNET`) which are enabled by default in most Capacitor templates.
|
|
29
|
+
|
|
30
|
+
## Build
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install
|
|
34
|
+
npm run lint
|
|
35
|
+
npm run build
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
To fully verify the plugin and regenerate the API docs section below, run:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm run verify
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Publishing
|
|
45
|
+
|
|
46
|
+
This package is a standard Capacitor plugin and can be published to npm like any other npm package:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm version patch # or minor / major
|
|
50
|
+
git push --follow-tags
|
|
51
|
+
npm publish --access public
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Alternatively, once you have configured an `NPM_TOKEN` secret in your GitHub repository, you can create a GitHub Release and let the `Publish to npm` workflow handle building and publishing for you.
|
|
55
|
+
|
|
56
|
+
## API
|
|
57
|
+
|
|
58
|
+
<docgen-index>
|
|
59
|
+
|
|
60
|
+
* [`startDiscovery(...)`](#startdiscovery)
|
|
61
|
+
* [`stopDiscovery(...)`](#stopdiscovery)
|
|
62
|
+
* [`addListener('deviceFound', ...)`](#addlistenerdevicefound-)
|
|
63
|
+
* [`addListener('deviceLost', ...)`](#addlistenerdevicelost-)
|
|
64
|
+
* [`addListener('discoveryError', ...)`](#addlistenerdiscoveryerror-)
|
|
65
|
+
* [`removeAllListeners()`](#removealllisteners)
|
|
66
|
+
* [Interfaces](#interfaces)
|
|
67
|
+
* [Type Aliases](#type-aliases)
|
|
68
|
+
|
|
69
|
+
</docgen-index>
|
|
70
|
+
|
|
71
|
+
<docgen-api>
|
|
72
|
+
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
|
|
73
|
+
|
|
74
|
+
### startDiscovery(...)
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
startDiscovery(options?: StartDiscoveryOptions | undefined) => Promise<void>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
| Param | Type |
|
|
81
|
+
| ------------- | ----------------------------------------------------------------------- |
|
|
82
|
+
| **`options`** | <code><a href="#startdiscoveryoptions">StartDiscoveryOptions</a></code> |
|
|
83
|
+
|
|
84
|
+
--------------------
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
### stopDiscovery(...)
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
stopDiscovery(options?: StopDiscoveryOptions | undefined) => Promise<void>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
| Param | Type |
|
|
94
|
+
| ------------- | --------------------------------------------------------------------- |
|
|
95
|
+
| **`options`** | <code><a href="#stopdiscoveryoptions">StopDiscoveryOptions</a></code> |
|
|
96
|
+
|
|
97
|
+
--------------------
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
### addListener('deviceFound', ...)
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
addListener(eventName: 'deviceFound', listenerFunc: (device: MdnsDevice) => void) => Promise<{ remove: () => void; }>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
| Param | Type |
|
|
107
|
+
| ------------------ | ---------------------------------------------------------------------- |
|
|
108
|
+
| **`eventName`** | <code>'deviceFound'</code> |
|
|
109
|
+
| **`listenerFunc`** | <code>(device: <a href="#mdnsdevice">MdnsDevice</a>) => void</code> |
|
|
110
|
+
|
|
111
|
+
**Returns:** <code>Promise<{ remove: () => void; }></code>
|
|
112
|
+
|
|
113
|
+
--------------------
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
### addListener('deviceLost', ...)
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
addListener(eventName: 'deviceLost', listenerFunc: (device: Pick<MdnsDevice, 'name' | 'serviceType'>) => void) => Promise<{ remove: () => void; }>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
| Param | Type |
|
|
123
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
124
|
+
| **`eventName`** | <code>'deviceLost'</code> |
|
|
125
|
+
| **`listenerFunc`** | <code>(device: <a href="#pick">Pick</a><<a href="#mdnsdevice">MdnsDevice</a>, 'name' \| 'serviceType'>) => void</code> |
|
|
126
|
+
|
|
127
|
+
**Returns:** <code>Promise<{ remove: () => void; }></code>
|
|
128
|
+
|
|
129
|
+
--------------------
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
### addListener('discoveryError', ...)
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
addListener(eventName: 'discoveryError', listenerFunc: (error: { message: string; code: number; }) => void) => Promise<{ remove: () => void; }>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
| Param | Type |
|
|
139
|
+
| ------------------ | ------------------------------------------------------------------- |
|
|
140
|
+
| **`eventName`** | <code>'discoveryError'</code> |
|
|
141
|
+
| **`listenerFunc`** | <code>(error: { message: string; code: number; }) => void</code> |
|
|
142
|
+
|
|
143
|
+
**Returns:** <code>Promise<{ remove: () => void; }></code>
|
|
144
|
+
|
|
145
|
+
--------------------
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
### removeAllListeners()
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
removeAllListeners() => Promise<void>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
--------------------
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
### Interfaces
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
#### StartDiscoveryOptions
|
|
161
|
+
|
|
162
|
+
| Prop | Type |
|
|
163
|
+
| ----------------- | ------------------- |
|
|
164
|
+
| **`serviceType`** | <code>string</code> |
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
#### StopDiscoveryOptions
|
|
168
|
+
|
|
169
|
+
| Prop | Type |
|
|
170
|
+
| ----------------- | ------------------- |
|
|
171
|
+
| **`serviceType`** | <code>string</code> |
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
#### MdnsDevice
|
|
175
|
+
|
|
176
|
+
| Prop | Type |
|
|
177
|
+
| ----------------- | --------------------------------------------------------------- |
|
|
178
|
+
| **`name`** | <code>string</code> |
|
|
179
|
+
| **`host`** | <code>string</code> |
|
|
180
|
+
| **`port`** | <code>number</code> |
|
|
181
|
+
| **`serviceType`** | <code>string</code> |
|
|
182
|
+
| **`txtRecords`** | <code><a href="#record">Record</a><string, string></code> |
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
### Type Aliases
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
#### Record
|
|
189
|
+
|
|
190
|
+
Construct a type with a set of properties K of type T
|
|
191
|
+
|
|
192
|
+
<code>{
|
|
193
|
[P in K]: T;
|
|
1
194
|
}</code>
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
#### Pick
|
|
198
|
+
|
|
199
|
+
From T, pick a set of properties whose keys are in the union K
|
|
200
|
+
|
|
201
|
+
<code>{
|
|
2
202
|
[P in K]: T[P];
|
|
3
203
|
}</code>
|
|
204
|
+
|
|
205
|
+
</docgen-api>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
ext {
|
|
2
|
+
junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
|
|
3
|
+
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
|
|
4
|
+
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
|
|
5
|
+
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
buildscript {
|
|
9
|
+
repositories {
|
|
10
|
+
google()
|
|
11
|
+
mavenCentral()
|
|
12
|
+
}
|
|
13
|
+
dependencies {
|
|
14
|
+
classpath 'com.android.tools.build:gradle:8.13.0'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
apply plugin: 'com.android.library'
|
|
19
|
+
apply plugin: 'org.jetbrains.kotlin.android'
|
|
20
|
+
|
|
21
|
+
android {
|
|
22
|
+
namespace = "com.rajendrakhope.plugins.mdns"
|
|
23
|
+
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
|
|
24
|
+
defaultConfig {
|
|
25
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
|
|
26
|
+
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
|
|
27
|
+
versionCode 1
|
|
28
|
+
versionName "1.0"
|
|
29
|
+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
30
|
+
}
|
|
31
|
+
buildTypes {
|
|
32
|
+
release {
|
|
33
|
+
minifyEnabled false
|
|
34
|
+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
lintOptions {
|
|
38
|
+
abortOnError = false
|
|
39
|
+
}
|
|
40
|
+
compileOptions {
|
|
41
|
+
sourceCompatibility JavaVersion.VERSION_21
|
|
42
|
+
targetCompatibility JavaVersion.VERSION_21
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
repositories {
|
|
47
|
+
google()
|
|
48
|
+
mavenCentral()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
dependencies {
|
|
53
|
+
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
54
|
+
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.22'
|
|
55
|
+
implementation project(':capacitor-android')
|
|
56
|
+
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
|
57
|
+
testImplementation "junit:junit:$junitVersion"
|
|
58
|
+
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
59
|
+
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
60
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
|
|
4
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
5
|
+
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
|
6
|
+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
7
|
+
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
|
8
|
+
|
|
9
|
+
</manifest>
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
package com.rajendrakhope.plugins.mdns
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.net.nsd.NsdManager
|
|
5
|
+
import android.net.nsd.NsdServiceInfo
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import android.util.Log
|
|
8
|
+
import com.getcapacitor.JSObject
|
|
9
|
+
import com.getcapacitor.Plugin
|
|
10
|
+
import com.getcapacitor.PluginCall
|
|
11
|
+
import com.getcapacitor.PluginMethod
|
|
12
|
+
import com.getcapacitor.annotation.CapacitorPlugin
|
|
13
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
14
|
+
import java.util.concurrent.Executors
|
|
15
|
+
import java.util.concurrent.LinkedBlockingQueue
|
|
16
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
17
|
+
|
|
18
|
+
private const val TAG = "MdnsDiscovery"
|
|
19
|
+
|
|
20
|
+
@CapacitorPlugin(name = "MdnsDiscovery")
|
|
21
|
+
class MdnsDiscoveryPlugin : Plugin() {
|
|
22
|
+
|
|
23
|
+
private var nsdManager: NsdManager? = null
|
|
24
|
+
private val discoveryListeners = ConcurrentHashMap<String, NsdManager.DiscoveryListener>()
|
|
25
|
+
private val resolveQueue = LinkedBlockingQueue<NsdServiceInfo>()
|
|
26
|
+
private val isResolving = AtomicBoolean(false)
|
|
27
|
+
private val resolveExecutor = Executors.newSingleThreadExecutor()
|
|
28
|
+
|
|
29
|
+
@PluginMethod
|
|
30
|
+
fun startDiscovery(call: PluginCall) {
|
|
31
|
+
val serviceType = call.getString("serviceType", "_http._tcp")!!
|
|
32
|
+
.let { if (it.endsWith(".")) it else "$it." }
|
|
33
|
+
|
|
34
|
+
if (discoveryListeners.containsKey(serviceType)) {
|
|
35
|
+
call.resolve()
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
nsdManager = nsdManager ?: (context.getSystemService(Context.NSD_SERVICE) as NsdManager)
|
|
40
|
+
|
|
41
|
+
val listener = buildDiscoveryListener(serviceType)
|
|
42
|
+
discoveryListeners[serviceType] = listener
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
nsdManager!!.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, listener)
|
|
46
|
+
Log.i(TAG, "Discovery started for $serviceType")
|
|
47
|
+
call.resolve()
|
|
48
|
+
} catch (e: Exception) {
|
|
49
|
+
Log.e(TAG, "Failed to start discovery: ${e.message}")
|
|
50
|
+
discoveryListeners.remove(serviceType)
|
|
51
|
+
call.reject("Failed to start mDNS discovery: ${e.message}")
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@PluginMethod
|
|
56
|
+
fun stopDiscovery(call: PluginCall) {
|
|
57
|
+
val serviceType = call.getString("serviceType", "_http._tcp")!!
|
|
58
|
+
.let { if (it.endsWith(".")) it else "$it." }
|
|
59
|
+
|
|
60
|
+
val listener = discoveryListeners.remove(serviceType)
|
|
61
|
+
if (listener != null) {
|
|
62
|
+
try {
|
|
63
|
+
nsdManager?.stopServiceDiscovery(listener)
|
|
64
|
+
Log.i(TAG, "Discovery stopped for $serviceType")
|
|
65
|
+
} catch (e: Exception) {
|
|
66
|
+
Log.w(TAG, "Error stopping discovery: ${e.message}")
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
call.resolve()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
override fun handleOnDestroy() {
|
|
73
|
+
super.handleOnDestroy()
|
|
74
|
+
discoveryListeners.forEach { (_, listener) ->
|
|
75
|
+
try { nsdManager?.stopServiceDiscovery(listener) } catch (_: Exception) {}
|
|
76
|
+
}
|
|
77
|
+
discoveryListeners.clear()
|
|
78
|
+
resolveExecutor.shutdown()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private fun buildDiscoveryListener(serviceType: String): NsdManager.DiscoveryListener {
|
|
82
|
+
return object : NsdManager.DiscoveryListener {
|
|
83
|
+
|
|
84
|
+
override fun onDiscoveryStarted(regType: String) {
|
|
85
|
+
Log.d(TAG, "Discovery started: $regType")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
override fun onServiceFound(serviceInfo: NsdServiceInfo) {
|
|
89
|
+
Log.d(TAG, "Service found: ${serviceInfo.serviceName}")
|
|
90
|
+
resolveQueue.offer(serviceInfo)
|
|
91
|
+
processResolveQueue()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
override fun onServiceLost(serviceInfo: NsdServiceInfo) {
|
|
95
|
+
Log.d(TAG, "Service lost: ${serviceInfo.serviceName}")
|
|
96
|
+
val data = JSObject().apply {
|
|
97
|
+
put("name", serviceInfo.serviceName)
|
|
98
|
+
put("serviceType", serviceType.trimEnd('.'))
|
|
99
|
+
}
|
|
100
|
+
notifyListeners("deviceLost", data)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
override fun onDiscoveryStopped(serviceType: String) {
|
|
104
|
+
Log.d(TAG, "Discovery stopped: $serviceType")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
|
|
108
|
+
Log.e(TAG, "Start discovery failed: $errorCode")
|
|
109
|
+
discoveryListeners.remove(serviceType)
|
|
110
|
+
val err = JSObject().apply {
|
|
111
|
+
put("message", "Discovery failed to start")
|
|
112
|
+
put("code", errorCode)
|
|
113
|
+
}
|
|
114
|
+
notifyListeners("discoveryError", err)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
|
|
118
|
+
Log.e(TAG, "Stop discovery failed: $errorCode")
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private fun processResolveQueue() {
|
|
124
|
+
if (!isResolving.compareAndSet(false, true)) return
|
|
125
|
+
|
|
126
|
+
resolveExecutor.submit {
|
|
127
|
+
val serviceInfo = resolveQueue.poll()
|
|
128
|
+
if (serviceInfo == null) {
|
|
129
|
+
isResolving.set(false)
|
|
130
|
+
return@submit
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
134
|
+
resolveWithExecutor(serviceInfo)
|
|
135
|
+
} else {
|
|
136
|
+
resolveClassic(serviceInfo)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private fun resolveWithExecutor(serviceInfo: NsdServiceInfo) {
|
|
142
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return
|
|
143
|
+
|
|
144
|
+
nsdManager?.resolveService(serviceInfo, resolveExecutor, object : NsdManager.ResolveListener {
|
|
145
|
+
override fun onResolveFailed(info: NsdServiceInfo, errorCode: Int) {
|
|
146
|
+
Log.w(TAG, "Resolve failed for ${info.serviceName}: $errorCode")
|
|
147
|
+
finishResolve()
|
|
148
|
+
}
|
|
149
|
+
override fun onServiceResolved(info: NsdServiceInfo) {
|
|
150
|
+
Log.i(TAG, "Resolved: ${info.serviceName} → ${info.host?.hostAddress}:${info.port}")
|
|
151
|
+
emitDeviceFound(info)
|
|
152
|
+
finishResolve()
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
android.os.Handler(android.os.Looper.getMainLooper()).postDelayed({
|
|
157
|
+
finishResolve()
|
|
158
|
+
}, 3000)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private fun resolveClassic(serviceInfo: NsdServiceInfo) {
|
|
162
|
+
nsdManager?.resolveService(serviceInfo, object : NsdManager.ResolveListener {
|
|
163
|
+
override fun onResolveFailed(info: NsdServiceInfo, errorCode: Int) {
|
|
164
|
+
Log.w(TAG, "Resolve failed for ${info.serviceName}: $errorCode")
|
|
165
|
+
finishResolve()
|
|
166
|
+
}
|
|
167
|
+
override fun onServiceResolved(info: NsdServiceInfo) {
|
|
168
|
+
Log.i(TAG, "Resolved: ${info.serviceName} → ${info.host?.hostAddress}:${info.port}")
|
|
169
|
+
emitDeviceFound(info)
|
|
170
|
+
finishResolve()
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private fun finishResolve() {
|
|
176
|
+
isResolving.set(false)
|
|
177
|
+
if (resolveQueue.isNotEmpty()) {
|
|
178
|
+
processResolveQueue()
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private fun emitDeviceFound(info: NsdServiceInfo) {
|
|
183
|
+
val hostAddress = info.host?.hostAddress ?: return
|
|
184
|
+
|
|
185
|
+
val txtRecords = JSObject()
|
|
186
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
187
|
+
info.attributes?.forEach { (key, valueBytes) ->
|
|
188
|
+
val value = valueBytes?.let { String(it, Charsets.UTF_8) } ?: ""
|
|
189
|
+
txtRecords.put(key, value)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
val data = JSObject().apply {
|
|
194
|
+
put("name", info.serviceName ?: "")
|
|
195
|
+
put("host", hostAddress)
|
|
196
|
+
put("port", info.port)
|
|
197
|
+
put("serviceType", (info.serviceType ?: "").trimEnd('.'))
|
|
198
|
+
put("txtRecords", txtRecords)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
notifyListeners("deviceFound", data)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
File without changes
|
package/dist/docs.json
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
{
|
|
2
|
+
"api": {
|
|
3
|
+
"name": "MdnsDiscoveryPlugin",
|
|
4
|
+
"slug": "mdnsdiscoveryplugin",
|
|
5
|
+
"docs": "",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"methods": [
|
|
8
|
+
{
|
|
9
|
+
"name": "startDiscovery",
|
|
10
|
+
"signature": "(options?: StartDiscoveryOptions | undefined) => Promise<void>",
|
|
11
|
+
"parameters": [
|
|
12
|
+
{
|
|
13
|
+
"name": "options",
|
|
14
|
+
"docs": "",
|
|
15
|
+
"type": "StartDiscoveryOptions | undefined"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"returns": "Promise<void>",
|
|
19
|
+
"tags": [],
|
|
20
|
+
"docs": "",
|
|
21
|
+
"complexTypes": [
|
|
22
|
+
"StartDiscoveryOptions"
|
|
23
|
+
],
|
|
24
|
+
"slug": "startdiscovery"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "stopDiscovery",
|
|
28
|
+
"signature": "(options?: StopDiscoveryOptions | undefined) => Promise<void>",
|
|
29
|
+
"parameters": [
|
|
30
|
+
{
|
|
31
|
+
"name": "options",
|
|
32
|
+
"docs": "",
|
|
33
|
+
"type": "StopDiscoveryOptions | undefined"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"returns": "Promise<void>",
|
|
37
|
+
"tags": [],
|
|
38
|
+
"docs": "",
|
|
39
|
+
"complexTypes": [
|
|
40
|
+
"StopDiscoveryOptions"
|
|
41
|
+
],
|
|
42
|
+
"slug": "stopdiscovery"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "addListener",
|
|
46
|
+
"signature": "(eventName: 'deviceFound', listenerFunc: (device: MdnsDevice) => void) => Promise<{ remove: () => void; }>",
|
|
47
|
+
"parameters": [
|
|
48
|
+
{
|
|
49
|
+
"name": "eventName",
|
|
50
|
+
"docs": "",
|
|
51
|
+
"type": "'deviceFound'"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "listenerFunc",
|
|
55
|
+
"docs": "",
|
|
56
|
+
"type": "(device: MdnsDevice) => void"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"returns": "Promise<{ remove: () => void; }>",
|
|
60
|
+
"tags": [],
|
|
61
|
+
"docs": "",
|
|
62
|
+
"complexTypes": [
|
|
63
|
+
"MdnsDevice"
|
|
64
|
+
],
|
|
65
|
+
"slug": "addlistenerdevicefound-"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "addListener",
|
|
69
|
+
"signature": "(eventName: 'deviceLost', listenerFunc: (device: Pick<MdnsDevice, 'name' | 'serviceType'>) => void) => Promise<{ remove: () => void; }>",
|
|
70
|
+
"parameters": [
|
|
71
|
+
{
|
|
72
|
+
"name": "eventName",
|
|
73
|
+
"docs": "",
|
|
74
|
+
"type": "'deviceLost'"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "listenerFunc",
|
|
78
|
+
"docs": "",
|
|
79
|
+
"type": "(device: Pick<MdnsDevice, 'name' | 'serviceType'>) => void"
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
"returns": "Promise<{ remove: () => void; }>",
|
|
83
|
+
"tags": [],
|
|
84
|
+
"docs": "",
|
|
85
|
+
"complexTypes": [
|
|
86
|
+
"Pick",
|
|
87
|
+
"MdnsDevice"
|
|
88
|
+
],
|
|
89
|
+
"slug": "addlistenerdevicelost-"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"name": "addListener",
|
|
93
|
+
"signature": "(eventName: 'discoveryError', listenerFunc: (error: { message: string; code: number; }) => void) => Promise<{ remove: () => void; }>",
|
|
94
|
+
"parameters": [
|
|
95
|
+
{
|
|
96
|
+
"name": "eventName",
|
|
97
|
+
"docs": "",
|
|
98
|
+
"type": "'discoveryError'"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"name": "listenerFunc",
|
|
102
|
+
"docs": "",
|
|
103
|
+
"type": "(error: { message: string; code: number; }) => void"
|
|
104
|
+
}
|
|
105
|
+
],
|
|
106
|
+
"returns": "Promise<{ remove: () => void; }>",
|
|
107
|
+
"tags": [],
|
|
108
|
+
"docs": "",
|
|
109
|
+
"complexTypes": [],
|
|
110
|
+
"slug": "addlistenerdiscoveryerror-"
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"name": "removeAllListeners",
|
|
114
|
+
"signature": "() => Promise<void>",
|
|
115
|
+
"parameters": [],
|
|
116
|
+
"returns": "Promise<void>",
|
|
117
|
+
"tags": [],
|
|
118
|
+
"docs": "",
|
|
119
|
+
"complexTypes": [],
|
|
120
|
+
"slug": "removealllisteners"
|
|
121
|
+
}
|
|
122
|
+
],
|
|
123
|
+
"properties": []
|
|
124
|
+
},
|
|
125
|
+
"interfaces": [
|
|
126
|
+
{
|
|
127
|
+
"name": "StartDiscoveryOptions",
|
|
128
|
+
"slug": "startdiscoveryoptions",
|
|
129
|
+
"docs": "",
|
|
130
|
+
"tags": [],
|
|
131
|
+
"methods": [],
|
|
132
|
+
"properties": [
|
|
133
|
+
{
|
|
134
|
+
"name": "serviceType",
|
|
135
|
+
"tags": [],
|
|
136
|
+
"docs": "",
|
|
137
|
+
"complexTypes": [],
|
|
138
|
+
"type": "string | undefined"
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"name": "StopDiscoveryOptions",
|
|
144
|
+
"slug": "stopdiscoveryoptions",
|
|
145
|
+
"docs": "",
|
|
146
|
+
"tags": [],
|
|
147
|
+
"methods": [],
|
|
148
|
+
"properties": [
|
|
149
|
+
{
|
|
150
|
+
"name": "serviceType",
|
|
151
|
+
"tags": [],
|
|
152
|
+
"docs": "",
|
|
153
|
+
"complexTypes": [],
|
|
154
|
+
"type": "string | undefined"
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"name": "MdnsDevice",
|
|
160
|
+
"slug": "mdnsdevice",
|
|
161
|
+
"docs": "",
|
|
162
|
+
"tags": [],
|
|
163
|
+
"methods": [],
|
|
164
|
+
"properties": [
|
|
165
|
+
{
|
|
166
|
+
"name": "name",
|
|
167
|
+
"tags": [],
|
|
168
|
+
"docs": "",
|
|
169
|
+
"complexTypes": [],
|
|
170
|
+
"type": "string"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"name": "host",
|
|
174
|
+
"tags": [],
|
|
175
|
+
"docs": "",
|
|
176
|
+
"complexTypes": [],
|
|
177
|
+
"type": "string"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"name": "port",
|
|
181
|
+
"tags": [],
|
|
182
|
+
"docs": "",
|
|
183
|
+
"complexTypes": [],
|
|
184
|
+
"type": "number"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"name": "serviceType",
|
|
188
|
+
"tags": [],
|
|
189
|
+
"docs": "",
|
|
190
|
+
"complexTypes": [],
|
|
191
|
+
"type": "string"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"name": "txtRecords",
|
|
195
|
+
"tags": [],
|
|
196
|
+
"docs": "",
|
|
197
|
+
"complexTypes": [
|
|
198
|
+
"Record"
|
|
199
|
+
],
|
|
200
|
+
"type": "Record<string, string>"
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
],
|
|
205
|
+
"enums": [],
|
|
206
|
+
"typeAliases": [
|
|
207
|
+
{
|
|
208
|
+
"name": "Record",
|
|
209
|
+
"slug": "record",
|
|
210
|
+
"docs": "Construct a type with a set of properties K of type T",
|
|
211
|
+
"types": [
|
|
212
|
+
{
|
|
213
|
+
"text": "{\r\n [P in K]: T;\r\n}",
|
|
214
|
+
"complexTypes": [
|
|
215
|
+
"K",
|
|
216
|
+
"T"
|
|
217
|
+
]
|
|
218
|
+
}
|
|
219
|
+
]
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"name": "Pick",
|
|
223
|
+
"slug": "pick",
|
|
224
|
+
"docs": "From T, pick a set of properties whose keys are in the union K",
|
|
225
|
+
"types": [
|
|
226
|
+
{
|
|
227
|
+
"text": "{\r\n [P in K]: T[P];\r\n}",
|
|
228
|
+
"complexTypes": [
|
|
229
|
+
"K",
|
|
230
|
+
"T",
|
|
231
|
+
"P"
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
}
|
|
236
|
+
],
|
|
237
|
+
"pluginConfigs": []
|
|
238
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface MdnsDevice {
|
|
2
|
+
name: string;
|
|
3
|
+
host: string;
|
|
4
|
+
port: number;
|
|
5
|
+
serviceType: string;
|
|
6
|
+
txtRecords: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
export interface StartDiscoveryOptions {
|
|
9
|
+
serviceType?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface StopDiscoveryOptions {
|
|
12
|
+
serviceType?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface MdnsDiscoveryPlugin {
|
|
15
|
+
startDiscovery(options?: StartDiscoveryOptions): Promise<void>;
|
|
16
|
+
stopDiscovery(options?: StopDiscoveryOptions): Promise<void>;
|
|
17
|
+
addListener(eventName: 'deviceFound', listenerFunc: (device: MdnsDevice) => void): Promise<{
|
|
18
|
+
remove: () => void;
|
|
19
|
+
}>;
|
|
20
|
+
addListener(eventName: 'deviceLost', listenerFunc: (device: Pick<MdnsDevice, 'name' | 'serviceType'>) => void): Promise<{
|
|
21
|
+
remove: () => void;
|
|
22
|
+
}>;
|
|
23
|
+
addListener(eventName: 'discoveryError', listenerFunc: (error: {
|
|
24
|
+
message: string;
|
|
25
|
+
code: number;
|
|
26
|
+
}) => void): Promise<{
|
|
27
|
+
remove: () => void;
|
|
28
|
+
}>;
|
|
29
|
+
removeAllListeners(): Promise<void>;
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["export interface MdnsDevice {\n name: string;\n host: string;\n port: number;\n serviceType: string;\n txtRecords: Record<string, string>;\n}\n\nexport interface StartDiscoveryOptions {\n serviceType?: string;\n}\n\nexport interface StopDiscoveryOptions {\n serviceType?: string;\n}\n\nexport interface MdnsDiscoveryPlugin {\n startDiscovery(options?: StartDiscoveryOptions): Promise<void>;\n stopDiscovery(options?: StopDiscoveryOptions): Promise<void>;\n addListener(\n eventName: 'deviceFound',\n listenerFunc: (device: MdnsDevice) => void\n ): Promise<{ remove: () => void }>;\n addListener(\n eventName: 'deviceLost',\n listenerFunc: (device: Pick<MdnsDevice, 'name' | 'serviceType'>) => void\n ): Promise<{ remove: () => void }>;\n addListener(\n eventName: 'discoveryError',\n listenerFunc: (error: { message: string; code: number }) => void\n ): Promise<{ remove: () => void }>;\n removeAllListeners(): Promise<void>;\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { registerPlugin } from '@capacitor/core';
|
|
2
|
+
const MdnsDiscovery = registerPlugin('MdnsDiscovery', {
|
|
3
|
+
web: () => import('./web').then(m => new m.MdnsDiscoveryWeb()),
|
|
4
|
+
});
|
|
5
|
+
export * from './definitions';
|
|
6
|
+
export { MdnsDiscovery };
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjD,MAAM,aAAa,GAAG,cAAc,CAAsB,eAAe,EAAE;IACzE,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,gBAAgB,EAAE,CAAC;CAC/D,CAAC,CAAC;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\nimport type { MdnsDiscoveryPlugin } from './definitions';\n\nconst MdnsDiscovery = registerPlugin<MdnsDiscoveryPlugin>('MdnsDiscovery', {\n web: () => import('./web').then(m => new m.MdnsDiscoveryWeb()),\n});\n\nexport * from './definitions';\nexport { MdnsDiscovery };\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { WebPlugin } from '@capacitor/core';
|
|
2
|
+
import type { MdnsDiscoveryPlugin, StartDiscoveryOptions, StopDiscoveryOptions } from './definitions';
|
|
3
|
+
export declare class MdnsDiscoveryWeb extends WebPlugin implements MdnsDiscoveryPlugin {
|
|
4
|
+
startDiscovery(_options?: StartDiscoveryOptions): Promise<void>;
|
|
5
|
+
stopDiscovery(_options?: StopDiscoveryOptions): Promise<void>;
|
|
6
|
+
}
|
package/dist/esm/web.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { WebPlugin } from '@capacitor/core';
|
|
2
|
+
export class MdnsDiscoveryWeb extends WebPlugin {
|
|
3
|
+
async startDiscovery(_options) {
|
|
4
|
+
console.warn('[MdnsDiscovery] Not supported in browser.');
|
|
5
|
+
}
|
|
6
|
+
async stopDiscovery(_options) {
|
|
7
|
+
console.warn('[MdnsDiscovery] Not supported in browser.');
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=web.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,MAAM,OAAO,gBAAiB,SAAQ,SAAS;IAC7C,KAAK,CAAC,cAAc,CAAC,QAAgC;QACnD,OAAO,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;IAC5D,CAAC;IACD,KAAK,CAAC,aAAa,CAAC,QAA+B;QACjD,OAAO,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;IAC5D,CAAC;CACF","sourcesContent":["import { WebPlugin } from '@capacitor/core';\nimport type { MdnsDiscoveryPlugin, StartDiscoveryOptions, StopDiscoveryOptions } from './definitions';\n\nexport class MdnsDiscoveryWeb extends WebPlugin implements MdnsDiscoveryPlugin {\n async startDiscovery(_options?: StartDiscoveryOptions): Promise<void> {\n console.warn('[MdnsDiscovery] Not supported in browser.');\n }\n async stopDiscovery(_options?: StopDiscoveryOptions): Promise<void> {\n console.warn('[MdnsDiscovery] Not supported in browser.');\n }\n}\n"]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@capacitor/core');
|
|
4
|
+
|
|
5
|
+
const MdnsDiscovery = core.registerPlugin('MdnsDiscovery', {
|
|
6
|
+
web: () => Promise.resolve().then(function () { return web; }).then(m => new m.MdnsDiscoveryWeb()),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
class MdnsDiscoveryWeb extends core.WebPlugin {
|
|
10
|
+
async startDiscovery(_options) {
|
|
11
|
+
console.warn('[MdnsDiscovery] Not supported in browser.');
|
|
12
|
+
}
|
|
13
|
+
async stopDiscovery(_options) {
|
|
14
|
+
console.warn('[MdnsDiscovery] Not supported in browser.');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
19
|
+
__proto__: null,
|
|
20
|
+
MdnsDiscoveryWeb: MdnsDiscoveryWeb
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
exports.MdnsDiscovery = MdnsDiscovery;
|
|
24
|
+
//# sourceMappingURL=plugin.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst MdnsDiscovery = registerPlugin('MdnsDiscovery', {\n web: () => import('./web').then(m => new m.MdnsDiscoveryWeb()),\n});\nexport * from './definitions';\nexport { MdnsDiscovery };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class MdnsDiscoveryWeb extends WebPlugin {\n async startDiscovery(_options) {\n console.warn('[MdnsDiscovery] Not supported in browser.');\n }\n async stopDiscovery(_options) {\n console.warn('[MdnsDiscovery] Not supported in browser.');\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;AACK,MAAC,aAAa,GAAGA,mBAAc,CAAC,eAAe,EAAE;AACtD,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,gBAAgB,EAAE,CAAC;AAClE,CAAC;;ACFM,MAAM,gBAAgB,SAASC,cAAS,CAAC;AAChD,IAAI,MAAM,cAAc,CAAC,QAAQ,EAAE;AACnC,QAAQ,OAAO,CAAC,IAAI,CAAC,2CAA2C,CAAC;AACjE,IAAI;AACJ,IAAI,MAAM,aAAa,CAAC,QAAQ,EAAE;AAClC,QAAQ,OAAO,CAAC,IAAI,CAAC,2CAA2C,CAAC;AACjE,IAAI;AACJ;;;;;;;;;"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
var capacitorMdnsDiscovery = (function (exports, core) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const MdnsDiscovery = core.registerPlugin('MdnsDiscovery', {
|
|
5
|
+
web: () => Promise.resolve().then(function () { return web; }).then(m => new m.MdnsDiscoveryWeb()),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
class MdnsDiscoveryWeb extends core.WebPlugin {
|
|
9
|
+
async startDiscovery(_options) {
|
|
10
|
+
console.warn('[MdnsDiscovery] Not supported in browser.');
|
|
11
|
+
}
|
|
12
|
+
async stopDiscovery(_options) {
|
|
13
|
+
console.warn('[MdnsDiscovery] Not supported in browser.');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
18
|
+
__proto__: null,
|
|
19
|
+
MdnsDiscoveryWeb: MdnsDiscoveryWeb
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
exports.MdnsDiscovery = MdnsDiscovery;
|
|
23
|
+
|
|
24
|
+
return exports;
|
|
25
|
+
|
|
26
|
+
})({}, capacitorExports);
|
|
27
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst MdnsDiscovery = registerPlugin('MdnsDiscovery', {\n web: () => import('./web').then(m => new m.MdnsDiscoveryWeb()),\n});\nexport * from './definitions';\nexport { MdnsDiscovery };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class MdnsDiscoveryWeb extends WebPlugin {\n async startDiscovery(_options) {\n console.warn('[MdnsDiscovery] Not supported in browser.');\n }\n async stopDiscovery(_options) {\n console.warn('[MdnsDiscovery] Not supported in browser.');\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,aAAa,GAAGA,mBAAc,CAAC,eAAe,EAAE;IACtD,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,gBAAgB,EAAE,CAAC;IAClE,CAAC;;ICFM,MAAM,gBAAgB,SAASC,cAAS,CAAC;IAChD,IAAI,MAAM,cAAc,CAAC,QAAQ,EAAE;IACnC,QAAQ,OAAO,CAAC,IAAI,CAAC,2CAA2C,CAAC;IACjE,IAAI;IACJ,IAAI,MAAM,aAAa,CAAC,QAAQ,EAAE;IAClC,QAAQ,OAAO,CAAC,IAAI,CAAC,2CAA2C,CAAC;IACjE,IAAI;IACJ;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Network
|
|
3
|
+
|
|
4
|
+
@objc public class MdnsDiscovery: NSObject, NetServiceBrowserDelegate, NetServiceDelegate {
|
|
5
|
+
private var browser: NetServiceBrowser?
|
|
6
|
+
private var services: [String: NetService] = [:]
|
|
7
|
+
|
|
8
|
+
@objc public var onDeviceFound: (([String: Any]) -> Void)?
|
|
9
|
+
@objc public var onDeviceLost: (([String: Any]) -> Void)?
|
|
10
|
+
@objc public var onError: (([String: Any]) -> Void)?
|
|
11
|
+
|
|
12
|
+
@objc public func startDiscovery(serviceType: String) {
|
|
13
|
+
stopAll()
|
|
14
|
+
|
|
15
|
+
let normalizedType = serviceType.hasSuffix(".") ? serviceType : "\(serviceType)."
|
|
16
|
+
|
|
17
|
+
let browser = NetServiceBrowser()
|
|
18
|
+
browser.delegate = self
|
|
19
|
+
self.browser = browser
|
|
20
|
+
|
|
21
|
+
browser.searchForServices(ofType: normalizedType, inDomain: "local.")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@objc public func stopDiscovery(serviceType: String?) {
|
|
25
|
+
stopAll()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@objc public func stopAll() {
|
|
29
|
+
browser?.stop()
|
|
30
|
+
browser?.delegate = nil
|
|
31
|
+
browser = nil
|
|
32
|
+
services.values.forEach { $0.stopMonitoring() }
|
|
33
|
+
services.removeAll()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public func netServiceBrowser(_ browser: NetServiceBrowser, didFind service: NetService, moreComing: Bool) {
|
|
37
|
+
service.delegate = self
|
|
38
|
+
let key = serviceKey(for: service)
|
|
39
|
+
services[key] = service
|
|
40
|
+
service.resolve(withTimeout: 5.0)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public func netServiceBrowser(_ browser: NetServiceBrowser, didRemove service: NetService, moreComing: Bool) {
|
|
44
|
+
let key = serviceKey(for: service)
|
|
45
|
+
services.removeValue(forKey: key)
|
|
46
|
+
|
|
47
|
+
let data: [String: Any] = [
|
|
48
|
+
"name": service.name,
|
|
49
|
+
"serviceType": service.type.trimmingCharacters(in: CharacterSet(charactersIn: "."))
|
|
50
|
+
]
|
|
51
|
+
onDeviceLost?(data)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public func netServiceBrowser(_ browser: NetServiceBrowser, didNotSearch errorDict: [String: NSNumber]) {
|
|
55
|
+
let code = errorDict[NetService.errorCode]?.intValue ?? -1
|
|
56
|
+
let data: [String: Any] = [
|
|
57
|
+
"message": "mDNS discovery failed to start",
|
|
58
|
+
"code": code
|
|
59
|
+
]
|
|
60
|
+
onError?(data)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public func netServiceDidResolveAddress(_ sender: NetService) {
|
|
64
|
+
let host = sender.hostName ?? ""
|
|
65
|
+
let port = sender.port
|
|
66
|
+
|
|
67
|
+
var txtRecords: [String: String] = [:]
|
|
68
|
+
if let txtData = sender.txtRecordData() {
|
|
69
|
+
let dict = NetService.dictionary(fromTXTRecord: txtData)
|
|
70
|
+
for (key, valueData) in dict {
|
|
71
|
+
let value = String(data: valueData, encoding: .utf8) ?? ""
|
|
72
|
+
txtRecords[key] = value
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let data: [String: Any] = [
|
|
77
|
+
"name": sender.name,
|
|
78
|
+
"host": host,
|
|
79
|
+
"port": port,
|
|
80
|
+
"serviceType": sender.type.trimmingCharacters(in: CharacterSet(charactersIn: ".")),
|
|
81
|
+
"txtRecords": txtRecords
|
|
82
|
+
]
|
|
83
|
+
onDeviceFound?(data)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public func netService(_ sender: NetService, didNotResolve errorDict: [String: NSNumber]) {
|
|
87
|
+
let code = errorDict[NetService.errorCode]?.intValue ?? -1
|
|
88
|
+
let data: [String: Any] = [
|
|
89
|
+
"message": "Failed to resolve service",
|
|
90
|
+
"code": code
|
|
91
|
+
]
|
|
92
|
+
onError?(data)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private func serviceKey(for service: NetService) -> String {
|
|
96
|
+
return "\(service.name).\((service.type))"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
|
|
4
|
+
@objc(MdnsDiscoveryPlugin)
|
|
5
|
+
public class MdnsDiscoveryPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
6
|
+
public let identifier = "MdnsDiscoveryPlugin"
|
|
7
|
+
public let jsName = "MdnsDiscovery"
|
|
8
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
9
|
+
CAPPluginMethod(name: "startDiscovery", returnType: CAPPluginReturnPromise),
|
|
10
|
+
CAPPluginMethod(name: "stopDiscovery", returnType: CAPPluginReturnPromise)
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
private let discovery = MdnsDiscovery()
|
|
14
|
+
|
|
15
|
+
public override func load() {
|
|
16
|
+
super.load()
|
|
17
|
+
|
|
18
|
+
discovery.onDeviceFound = { [weak self] data in
|
|
19
|
+
self?.notifyListeners("deviceFound", data: data)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
discovery.onDeviceLost = { [weak self] data in
|
|
23
|
+
self?.notifyListeners("deviceLost", data: data)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
discovery.onError = { [weak self] data in
|
|
27
|
+
self?.notifyListeners("discoveryError", data: data)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@objc func startDiscovery(_ call: CAPPluginCall) {
|
|
32
|
+
let serviceType = call.getString("serviceType") ?? "_http._tcp"
|
|
33
|
+
discovery.startDiscovery(serviceType: serviceType)
|
|
34
|
+
call.resolve()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@objc func stopDiscovery(_ call: CAPPluginCall) {
|
|
38
|
+
let serviceType = call.getString("serviceType")
|
|
39
|
+
discovery.stopDiscovery(serviceType: serviceType)
|
|
40
|
+
call.resolve()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public override func handleOnDestroy() {
|
|
44
|
+
super.handleOnDestroy()
|
|
45
|
+
discovery.stopAll()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import MdnsDiscoveryPlugin
|
|
3
|
+
|
|
4
|
+
class MdnsDiscoveryTests: XCTestCase {
|
|
5
|
+
func testEcho() {
|
|
6
|
+
let implementation = MdnsDiscovery()
|
|
7
|
+
implementation.startDiscovery(serviceType: "_http._tcp")
|
|
8
|
+
implementation.stopDiscovery(serviceType: "_http._tcp")
|
|
9
|
+
implementation.stopAll()
|
|
10
|
+
XCTAssertTrue(true)
|
|
11
|
+
}
|
|
12
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "capacitor-mdns-discovery",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "mDNS device discovery for IoT Devices",
|
|
5
|
+
"main": "dist/plugin.cjs.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"unpkg": "dist/plugin.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"android/src/main/",
|
|
11
|
+
"android/build.gradle",
|
|
12
|
+
"dist/",
|
|
13
|
+
"ios/Sources",
|
|
14
|
+
"ios/Tests",
|
|
15
|
+
"Package.swift",
|
|
16
|
+
"CapacitorMdnsDiscovery.podspec"
|
|
17
|
+
],
|
|
18
|
+
"author": "Rajendra Khope",
|
|
19
|
+
"license": "Apache-2.0",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/bkrajendra/capacitor-mdns-discovery.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/bkrajendra/capacitor-mdns-discovery/issues"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"capacitor",
|
|
29
|
+
"plugin",
|
|
30
|
+
"native"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
34
|
+
"verify:ios": "xcodebuild -scheme CapacitorMdnsDiscovery -destination generic/platform=iOS",
|
|
35
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
36
|
+
"verify:web": "npm run build",
|
|
37
|
+
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
38
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
|
|
39
|
+
"eslint": "eslint . --ext ts",
|
|
40
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
41
|
+
"swiftlint": "node-swiftlint",
|
|
42
|
+
"docgen": "docgen --api MdnsDiscoveryPlugin --output-readme README.md --output-json dist/docs.json",
|
|
43
|
+
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
44
|
+
"clean": "rimraf ./dist",
|
|
45
|
+
"watch": "tsc --watch",
|
|
46
|
+
"prepublishOnly": "npm run build"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@capacitor/android": "^8.0.0",
|
|
50
|
+
"@capacitor/core": "^8.0.0",
|
|
51
|
+
"@capacitor/docgen": "^0.3.1",
|
|
52
|
+
"@capacitor/ios": "^8.0.0",
|
|
53
|
+
"@ionic/eslint-config": "^0.4.0",
|
|
54
|
+
"@ionic/prettier-config": "^4.0.0",
|
|
55
|
+
"@ionic/swiftlint-config": "^2.0.0",
|
|
56
|
+
"eslint": "^8.57.1",
|
|
57
|
+
"prettier": "^3.6.2",
|
|
58
|
+
"prettier-plugin-java": "^2.7.7",
|
|
59
|
+
"rimraf": "^6.1.0",
|
|
60
|
+
"rollup": "^4.59.0",
|
|
61
|
+
"swiftlint": "^2.0.0",
|
|
62
|
+
"typescript": "^5.9.3"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"@capacitor/core": ">=8.0.0"
|
|
66
|
+
},
|
|
67
|
+
"prettier": "@ionic/prettier-config",
|
|
68
|
+
"swiftlint": "@ionic/swiftlint-config",
|
|
69
|
+
"eslintConfig": {
|
|
70
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
71
|
+
},
|
|
72
|
+
"capacitor": {
|
|
73
|
+
"ios": {
|
|
74
|
+
"src": "ios"
|
|
75
|
+
},
|
|
76
|
+
"android": {
|
|
77
|
+
"src": "android"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|