@dawidzawada/bonjour-zeroconf 1.0.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.
- package/BonjourZeroconf.podspec +30 -0
- package/LICENSE +20 -0
- package/README.md +35 -0
- package/android/CMakeLists.txt +24 -0
- package/android/build.gradle +128 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/margelo/nitro/dawidzawada/bonjourzeroconf/BonjourZeroconf+AddressResolver.kt +182 -0
- package/android/src/main/java/com/margelo/nitro/dawidzawada/bonjourzeroconf/BonjourZeroconf+Listeners.kt +45 -0
- package/android/src/main/java/com/margelo/nitro/dawidzawada/bonjourzeroconf/BonjourZeroconf.kt +183 -0
- package/android/src/main/java/com/margelo/nitro/dawidzawada/bonjourzeroconf/BonjourZeroconfPackage.kt +22 -0
- package/ios/AddressResolverError.swift +20 -0
- package/ios/BonjourZeroconf+AddressResolver.swift +133 -0
- package/ios/BonjourZeroconf+Listeners.swift +26 -0
- package/ios/BonjourZeroconf.swift +201 -0
- package/ios/LocalNetworkAuthorization.swift +66 -0
- package/ios/LocalNetworkPermission+Listeners.swift +14 -0
- package/ios/LocalNetworkPermission.swift +41 -0
- package/ios/ServiceCache.swift +30 -0
- package/ios/Utils/Loggy.swift +37 -0
- package/lib/module/index.js +9 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/permissions.ios.js +23 -0
- package/lib/module/permissions.ios.js.map +1 -0
- package/lib/module/permissions.js +9 -0
- package/lib/module/permissions.js.map +1 -0
- package/lib/module/specs/BonjourFail.js +9 -0
- package/lib/module/specs/BonjourFail.js.map +1 -0
- package/lib/module/specs/BonjourListener.js +2 -0
- package/lib/module/specs/BonjourListener.js.map +1 -0
- package/lib/module/specs/BonjourZeroconf.nitro.js +4 -0
- package/lib/module/specs/BonjourZeroconf.nitro.js.map +1 -0
- package/lib/module/specs/LocalNetworkPermission.nitro.js +4 -0
- package/lib/module/specs/LocalNetworkPermission.nitro.js.map +1 -0
- package/lib/module/specs/ScanResult.js +2 -0
- package/lib/module/specs/ScanResult.js.map +1 -0
- package/lib/module/useIsScanning.js +19 -0
- package/lib/module/useIsScanning.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/index.d.ts +9 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/permissions.d.ts +3 -0
- package/lib/typescript/src/permissions.d.ts.map +1 -0
- package/lib/typescript/src/permissions.ios.d.ts +3 -0
- package/lib/typescript/src/permissions.ios.d.ts.map +1 -0
- package/lib/typescript/src/specs/BonjourFail.d.ts +6 -0
- package/lib/typescript/src/specs/BonjourFail.d.ts.map +1 -0
- package/lib/typescript/src/specs/BonjourListener.d.ts +4 -0
- package/lib/typescript/src/specs/BonjourListener.d.ts.map +1 -0
- package/lib/typescript/src/specs/BonjourZeroconf.nitro.d.ts +19 -0
- package/lib/typescript/src/specs/BonjourZeroconf.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/LocalNetworkPermission.nitro.d.ts +9 -0
- package/lib/typescript/src/specs/LocalNetworkPermission.nitro.d.ts.map +1 -0
- package/lib/typescript/src/specs/ScanResult.d.ts +8 -0
- package/lib/typescript/src/specs/ScanResult.d.ts.map +1 -0
- package/lib/typescript/src/useIsScanning.d.ts +2 -0
- package/lib/typescript/src/useIsScanning.d.ts.map +1 -0
- package/nitro.json +20 -0
- package/nitrogen/generated/android/c++/JBonjourFail.hpp +62 -0
- package/nitrogen/generated/android/c++/JBonjourListener.hpp +68 -0
- package/nitrogen/generated/android/c++/JFunc_void.hpp +74 -0
- package/nitrogen/generated/android/c++/JFunc_void_BonjourFail.hpp +76 -0
- package/nitrogen/generated/android/c++/JFunc_void_bool.hpp +74 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_ScanResult_.hpp +97 -0
- package/nitrogen/generated/android/c++/JHybridBonjourZeroconfSpec.cpp +96 -0
- package/nitrogen/generated/android/c++/JHybridBonjourZeroconfSpec.hpp +69 -0
- package/nitrogen/generated/android/c++/JScanOptions.hpp +57 -0
- package/nitrogen/generated/android/c++/JScanResult.hpp +74 -0
- package/nitrogen/generated/android/dawidzawada_bonjourzeroconf+autolinking.cmake +82 -0
- package/nitrogen/generated/android/dawidzawada_bonjourzeroconf+autolinking.gradle +27 -0
- package/nitrogen/generated/android/dawidzawada_bonjourzeroconfOnLoad.cpp +52 -0
- package/nitrogen/generated/android/dawidzawada_bonjourzeroconfOnLoad.hpp +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dawidzawada/bonjourzeroconf/BonjourFail.kt +22 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dawidzawada/bonjourzeroconf/BonjourListener.kt +42 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dawidzawada/bonjourzeroconf/Func_void.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dawidzawada/bonjourzeroconf/Func_void_BonjourFail.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dawidzawada/bonjourzeroconf/Func_void_bool.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dawidzawada/bonjourzeroconf/Func_void_std__vector_ScanResult_.kt +80 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dawidzawada/bonjourzeroconf/HybridBonjourZeroconfSpec.kt +90 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dawidzawada/bonjourzeroconf/ScanOptions.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dawidzawada/bonjourzeroconf/ScanResult.kt +50 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/dawidzawada/bonjourzeroconf/dawidzawada_bonjourzeroconfOnLoad.kt +35 -0
- package/nitrogen/generated/ios/BonjourZeroconf+autolinking.rb +60 -0
- package/nitrogen/generated/ios/BonjourZeroconf-Swift-Cxx-Bridge.cpp +90 -0
- package/nitrogen/generated/ios/BonjourZeroconf-Swift-Cxx-Bridge.hpp +282 -0
- package/nitrogen/generated/ios/BonjourZeroconf-Swift-Cxx-Umbrella.hpp +65 -0
- package/nitrogen/generated/ios/BonjourZeroconfAutolinking.mm +41 -0
- package/nitrogen/generated/ios/BonjourZeroconfAutolinking.swift +40 -0
- package/nitrogen/generated/ios/c++/HybridBonjourZeroconfSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridBonjourZeroconfSpecSwift.hpp +120 -0
- package/nitrogen/generated/ios/c++/HybridLocalNetworkPermissionSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridLocalNetworkPermissionSpecSwift.hpp +87 -0
- package/nitrogen/generated/ios/swift/BonjourFail.swift +44 -0
- package/nitrogen/generated/ios/swift/BonjourListener.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_BonjourFail.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +47 -0
- package/nitrogen/generated/ios/swift/Func_void_std__vector_ScanResult_.swift +47 -0
- package/nitrogen/generated/ios/swift/HybridBonjourZeroconfSpec.swift +60 -0
- package/nitrogen/generated/ios/swift/HybridBonjourZeroconfSpec_cxx.swift +203 -0
- package/nitrogen/generated/ios/swift/HybridLocalNetworkPermissionSpec.swift +57 -0
- package/nitrogen/generated/ios/swift/HybridLocalNetworkPermissionSpec_cxx.swift +155 -0
- package/nitrogen/generated/ios/swift/ScanOptions.swift +48 -0
- package/nitrogen/generated/ios/swift/ScanResult.swift +149 -0
- package/nitrogen/generated/shared/c++/BonjourFail.hpp +63 -0
- package/nitrogen/generated/shared/c++/BonjourListener.hpp +75 -0
- package/nitrogen/generated/shared/c++/HybridBonjourZeroconfSpec.cpp +26 -0
- package/nitrogen/generated/shared/c++/HybridBonjourZeroconfSpec.hpp +80 -0
- package/nitrogen/generated/shared/c++/HybridLocalNetworkPermissionSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridLocalNetworkPermissionSpec.hpp +66 -0
- package/nitrogen/generated/shared/c++/ScanOptions.hpp +75 -0
- package/nitrogen/generated/shared/c++/ScanResult.hpp +92 -0
- package/package.json +169 -0
- package/src/index.ts +22 -0
- package/src/permissions.ios.ts +27 -0
- package/src/permissions.ts +7 -0
- package/src/specs/BonjourFail.ts +5 -0
- package/src/specs/BonjourListener.ts +3 -0
- package/src/specs/BonjourZeroconf.nitro.ts +20 -0
- package/src/specs/LocalNetworkPermission.nitro.ts +7 -0
- package/src/specs/ScanResult.ts +7 -0
- package/src/useIsScanning.ts +17 -0
|
@@ -0,0 +1,30 @@
|
|
|
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 = "BonjourZeroconf"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://github.com/dawidzawada/bonjour-zeroconf.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
s.source_files = [
|
|
18
|
+
"ios/**/*.{swift}",
|
|
19
|
+
"ios/**/*.{m,mm}",
|
|
20
|
+
"cpp/**/*.{hpp,cpp}",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
s.dependency 'React-jsi'
|
|
24
|
+
s.dependency 'React-callinvoker'
|
|
25
|
+
|
|
26
|
+
load 'nitrogen/generated/ios/BonjourZeroconf+autolinking.rb'
|
|
27
|
+
add_nitrogen_files(s)
|
|
28
|
+
|
|
29
|
+
install_modules_dependencies(s)
|
|
30
|
+
end
|
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Dawid Zawada
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @dawidzawada/bonjour-zeroconf
|
|
2
|
+
|
|
3
|
+
Zeroconf devices scanner using Bonjour (iOS) and NSD (Android) for React Native & Expo apps. Powered by Nitro Modules.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @dawidzawada/bonjour-zeroconf react-native-nitro-modules
|
|
9
|
+
|
|
10
|
+
> `react-native-nitro-modules` is required as this library relies on [Nitro Modules](https://nitro.margelo.com/).
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
import { Scanner } from '@dawidzawada/bonjour-zeroconf';
|
|
17
|
+
|
|
18
|
+
// ...
|
|
19
|
+
|
|
20
|
+
Scanner.scan('_bonjour._tcp', 'local');
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Contributing
|
|
24
|
+
|
|
25
|
+
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
26
|
+
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
|
|
27
|
+
- [Code of conduct](CODE_OF_CONDUCT.md)
|
|
28
|
+
|
|
29
|
+
## License
|
|
30
|
+
|
|
31
|
+
MIT
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
project(dawidzawada_bonjourzeroconf)
|
|
2
|
+
cmake_minimum_required(VERSION 3.9.0)
|
|
3
|
+
|
|
4
|
+
set(PACKAGE_NAME dawidzawada_bonjourzeroconf)
|
|
5
|
+
set(CMAKE_VERBOSE_MAKEFILE ON)
|
|
6
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
7
|
+
|
|
8
|
+
# Define C++ library and add all sources
|
|
9
|
+
add_library(${PACKAGE_NAME} SHARED src/main/cpp/cpp-adapter.cpp)
|
|
10
|
+
|
|
11
|
+
# Add Nitrogen specs :)
|
|
12
|
+
include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/dawidzawada_bonjourzeroconf+autolinking.cmake)
|
|
13
|
+
|
|
14
|
+
# Set up local includes
|
|
15
|
+
include_directories("src/main/cpp" "../cpp")
|
|
16
|
+
|
|
17
|
+
find_library(LOG_LIB log)
|
|
18
|
+
|
|
19
|
+
# Link all libraries together
|
|
20
|
+
target_link_libraries(
|
|
21
|
+
${PACKAGE_NAME}
|
|
22
|
+
${LOG_LIB}
|
|
23
|
+
android # <-- Android core
|
|
24
|
+
)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.getExtOrDefault = {name ->
|
|
3
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['BonjourZeroconf_' + name]
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
repositories {
|
|
7
|
+
google()
|
|
8
|
+
mavenCentral()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dependencies {
|
|
12
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
13
|
+
// noinspection DifferentKotlinGradleVersion
|
|
14
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def reactNativeArchitectures() {
|
|
19
|
+
def value = rootProject.getProperties().get("reactNativeArchitectures")
|
|
20
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
apply plugin: "com.android.library"
|
|
24
|
+
apply plugin: "kotlin-android"
|
|
25
|
+
apply from: '../nitrogen/generated/android/dawidzawada_bonjourzeroconf+autolinking.gradle'
|
|
26
|
+
|
|
27
|
+
apply plugin: "com.facebook.react"
|
|
28
|
+
|
|
29
|
+
def getExtOrIntegerDefault(name) {
|
|
30
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["BonjourZeroconf_" + name]).toInteger()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
android {
|
|
34
|
+
namespace "com.margelo.nitro.dawidzawada.bonjourzeroconf"
|
|
35
|
+
|
|
36
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
37
|
+
|
|
38
|
+
defaultConfig {
|
|
39
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
40
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
41
|
+
|
|
42
|
+
externalNativeBuild {
|
|
43
|
+
cmake {
|
|
44
|
+
cppFlags "-frtti -fexceptions -Wall -fstack-protector-all"
|
|
45
|
+
arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
|
|
46
|
+
abiFilters (*reactNativeArchitectures())
|
|
47
|
+
|
|
48
|
+
buildTypes {
|
|
49
|
+
debug {
|
|
50
|
+
cppFlags "-O1 -g"
|
|
51
|
+
}
|
|
52
|
+
release {
|
|
53
|
+
cppFlags "-O2"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
externalNativeBuild {
|
|
61
|
+
cmake {
|
|
62
|
+
path "CMakeLists.txt"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
packagingOptions {
|
|
67
|
+
excludes = [
|
|
68
|
+
"META-INF",
|
|
69
|
+
"META-INF/**",
|
|
70
|
+
"**/libc++_shared.so",
|
|
71
|
+
"**/libfbjni.so",
|
|
72
|
+
"**/libjsi.so",
|
|
73
|
+
"**/libfolly_json.so",
|
|
74
|
+
"**/libfolly_runtime.so",
|
|
75
|
+
"**/libglog.so",
|
|
76
|
+
"**/libhermes.so",
|
|
77
|
+
"**/libhermes-executor-debug.so",
|
|
78
|
+
"**/libhermes_executor.so",
|
|
79
|
+
"**/libreactnative.so",
|
|
80
|
+
"**/libreactnativejni.so",
|
|
81
|
+
"**/libturbomodulejsijni.so",
|
|
82
|
+
"**/libreact_nativemodule_core.so",
|
|
83
|
+
"**/libjscexecutor.so"
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
buildFeatures {
|
|
88
|
+
buildConfig true
|
|
89
|
+
prefab true
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
buildTypes {
|
|
93
|
+
release {
|
|
94
|
+
minifyEnabled false
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
lintOptions {
|
|
99
|
+
disable "GradleCompatible"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
compileOptions {
|
|
103
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
104
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
sourceSets {
|
|
108
|
+
main {
|
|
109
|
+
java.srcDirs += [
|
|
110
|
+
"generated/java",
|
|
111
|
+
"generated/jni"
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
repositories {
|
|
118
|
+
mavenCentral()
|
|
119
|
+
google()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
123
|
+
|
|
124
|
+
dependencies {
|
|
125
|
+
implementation "com.facebook.react:react-android"
|
|
126
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
127
|
+
implementation project(":react-native-nitro-modules")
|
|
128
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
package com.margelo.nitro.dawidzawada.bonjourzeroconf
|
|
2
|
+
|
|
3
|
+
import android.net.nsd.NsdManager
|
|
4
|
+
import android.net.nsd.NsdServiceInfo
|
|
5
|
+
import android.util.Log
|
|
6
|
+
import androidx.annotation.RequiresApi
|
|
7
|
+
import com.margelo.nitro.dawidzawada.bonjourzeroconf.BonjourZeroconf.Companion.TAG
|
|
8
|
+
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
9
|
+
import kotlinx.coroutines.sync.withLock
|
|
10
|
+
import kotlinx.coroutines.withTimeoutOrNull
|
|
11
|
+
import java.util.concurrent.Executors
|
|
12
|
+
|
|
13
|
+
suspend fun BonjourZeroconf.resolveService(service: NsdServiceInfo, serviceKey: String, timeout: Long) {
|
|
14
|
+
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
|
15
|
+
// New API (Android 14+)
|
|
16
|
+
resolveServiceNew(service, serviceKey, timeout)
|
|
17
|
+
} else {
|
|
18
|
+
resolveServiceLegacy(service, serviceKey, timeout)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@RequiresApi(34)
|
|
23
|
+
suspend fun BonjourZeroconf.resolveServiceNew(service: NsdServiceInfo, serviceKey: String, timeout: Long) {
|
|
24
|
+
try {
|
|
25
|
+
val resolved = withTimeoutOrNull(timeout) {
|
|
26
|
+
suspendCancellableCoroutine { continuation ->
|
|
27
|
+
val executor = Executors.newSingleThreadExecutor()
|
|
28
|
+
|
|
29
|
+
val manager = nsdManager
|
|
30
|
+
if (manager == null) {
|
|
31
|
+
executor.shutdown()
|
|
32
|
+
continuation.resume(null) {}
|
|
33
|
+
return@suspendCancellableCoroutine
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
val callback = object : NsdManager.ServiceInfoCallback {
|
|
37
|
+
fun unregisterCallback() {
|
|
38
|
+
try {
|
|
39
|
+
manager.unregisterServiceInfoCallback(this)
|
|
40
|
+
} catch (e: Exception) {
|
|
41
|
+
Log.e(TAG, "Error unregistering", e)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
override fun onServiceInfoCallbackRegistrationFailed(errorCode: Int) {
|
|
46
|
+
Log.e(TAG, "Registration failed: ${service.serviceName}, error: $errorCode")
|
|
47
|
+
notifyScanFailListeners(BonjourFail.RESOLVE_FAILED)
|
|
48
|
+
unregisterCallback()
|
|
49
|
+
continuation.resume(null) {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override fun onServiceUpdated(serviceInfo: NsdServiceInfo) {
|
|
53
|
+
Log.d(TAG, "Service updated: ${serviceInfo.serviceName}")
|
|
54
|
+
unregisterCallback()
|
|
55
|
+
continuation.resume(serviceInfo) {}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
override fun onServiceLost() {
|
|
59
|
+
Log.d(TAG, "Service lost during resolution: ${service.serviceName}")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
override fun onServiceInfoCallbackUnregistered() {
|
|
63
|
+
Log.d(TAG, "Callback unregistered: ${service.serviceName}")
|
|
64
|
+
executor.shutdown()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
manager.registerServiceInfoCallback(service, executor, callback)
|
|
70
|
+
|
|
71
|
+
continuation.invokeOnCancellation {
|
|
72
|
+
try {
|
|
73
|
+
manager.unregisterServiceInfoCallback(callback)
|
|
74
|
+
} catch (e: Exception) {
|
|
75
|
+
Log.e(TAG, "Error unregistering on cancellation", e)
|
|
76
|
+
executor.shutdown()
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} catch (e: Exception) {
|
|
80
|
+
notifyScanFailListeners(BonjourFail.RESOLVE_FAILED)
|
|
81
|
+
Log.e(TAG, "Exception registering callback", e)
|
|
82
|
+
executor.shutdown()
|
|
83
|
+
continuation.resume(null) {}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
resolved?.let { serviceInfo ->
|
|
89
|
+
extractScanResult(serviceInfo)?.let { scanResult ->
|
|
90
|
+
serviceCache[serviceKey] = scanResult
|
|
91
|
+
notifyScanResultsListeners()
|
|
92
|
+
}
|
|
93
|
+
} ?: Log.w(TAG, "Failed to resolve service: $serviceKey")
|
|
94
|
+
|
|
95
|
+
} catch (e: Exception) {
|
|
96
|
+
notifyScanFailListeners(BonjourFail.RESOLVE_FAILED)
|
|
97
|
+
Log.e(TAG, "Error during service resolution", e)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@Suppress("DEPRECATION")
|
|
102
|
+
suspend fun BonjourZeroconf.resolveServiceLegacy(service: NsdServiceInfo, serviceKey: String, timeout: Long) {
|
|
103
|
+
try {
|
|
104
|
+
val resolved = legacyResolveMutex.withLock {
|
|
105
|
+
withTimeoutOrNull(timeout) {
|
|
106
|
+
suspendCancellableCoroutine { continuation ->
|
|
107
|
+
val resolveListener = object : NsdManager.ResolveListener {
|
|
108
|
+
override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
|
|
109
|
+
Log.e(TAG, "Resolve failed: ${serviceInfo.serviceName}, error: $errorCode")
|
|
110
|
+
|
|
111
|
+
notifyScanFailListeners(BonjourFail.RESOLVE_FAILED)
|
|
112
|
+
continuation.resume(null) {}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
|
|
116
|
+
Log.d(TAG, "Service resolved: ${serviceInfo.serviceName}")
|
|
117
|
+
continuation.resume(serviceInfo) {}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
val manager = nsdManager
|
|
123
|
+
if (manager == null) {
|
|
124
|
+
continuation.resume(null) {}
|
|
125
|
+
return@suspendCancellableCoroutine
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
manager.resolveService(service, resolveListener)
|
|
129
|
+
} catch (e: Exception) {
|
|
130
|
+
notifyScanFailListeners(BonjourFail.RESOLVE_FAILED)
|
|
131
|
+
Log.e(TAG, "Exception resolving service", e)
|
|
132
|
+
continuation.resume(null) {}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
resolved?.let { serviceInfo ->
|
|
139
|
+
extractScanResult(serviceInfo)?.let { scanResult ->
|
|
140
|
+
serviceCache[serviceKey] = scanResult
|
|
141
|
+
notifyScanResultsListeners()
|
|
142
|
+
}
|
|
143
|
+
} ?: Log.w(TAG, "Failed to resolve service: $serviceKey")
|
|
144
|
+
|
|
145
|
+
} catch (e: Exception) {
|
|
146
|
+
Log.e(TAG, "Error during service resolution", e)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private fun BonjourZeroconf.extractScanResult(serviceInfo: NsdServiceInfo): ScanResult? {
|
|
151
|
+
return try {
|
|
152
|
+
val host = serviceInfo.host ?: return null
|
|
153
|
+
val port = serviceInfo.port
|
|
154
|
+
|
|
155
|
+
val (ipv4, ipv6) = when {
|
|
156
|
+
host.address.size == 4 -> host.hostAddress to null
|
|
157
|
+
host.address.size == 16 -> null to formatIPv6Address(host.address)
|
|
158
|
+
else -> null to null
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
ScanResult(
|
|
162
|
+
name = serviceInfo.serviceName,
|
|
163
|
+
ipv4 = ipv4,
|
|
164
|
+
ipv6 = ipv6,
|
|
165
|
+
hostname = host.hostName,
|
|
166
|
+
port = port.toDouble()
|
|
167
|
+
)
|
|
168
|
+
} catch (e: Exception) {
|
|
169
|
+
notifyScanFailListeners(BonjourFail.EXTRACTION_FAILED)
|
|
170
|
+
Log.e(TAG, "Failed to extract scan result", e)
|
|
171
|
+
null
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private fun formatIPv6Address(bytes: ByteArray): String {
|
|
176
|
+
require(bytes.size == 16) { "IPv6 address must be 16 bytes" }
|
|
177
|
+
|
|
178
|
+
return (0 until 16 step 2).joinToString(":") { i ->
|
|
179
|
+
val segment = ((bytes[i].toInt() and 0xFF) shl 8) or (bytes[i + 1].toInt() and 0xFF)
|
|
180
|
+
segment.toString(16)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
package com.margelo.nitro.dawidzawada.bonjourzeroconf
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.margelo.nitro.dawidzawada.bonjourzeroconf.BonjourZeroconf.Companion.TAG
|
|
5
|
+
import kotlinx.coroutines.Dispatchers
|
|
6
|
+
import kotlinx.coroutines.launch
|
|
7
|
+
|
|
8
|
+
fun BonjourZeroconf.notifyScanResultsListeners() {
|
|
9
|
+
val results = serviceCache.values.toTypedArray()
|
|
10
|
+
scope.launch(Dispatchers.Main) {
|
|
11
|
+
scanResultsListeners.values.forEach { listener ->
|
|
12
|
+
try {
|
|
13
|
+
listener(results)
|
|
14
|
+
} catch (e: Exception) {
|
|
15
|
+
Log.e(TAG, "Error notifying scan results listener", e)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fun BonjourZeroconf.updateScanningState(newState: Boolean) {
|
|
22
|
+
_isScanning = newState
|
|
23
|
+
scope.launch(Dispatchers.Main) {
|
|
24
|
+
scanStateListeners.values.forEach { listener ->
|
|
25
|
+
try {
|
|
26
|
+
listener(newState)
|
|
27
|
+
} catch (e: Exception) {
|
|
28
|
+
Log.e(TAG, "Error notifying scan state listener", e)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
fun BonjourZeroconf.notifyScanFailListeners(fail: BonjourFail) {
|
|
36
|
+
scope.launch(Dispatchers.Main) {
|
|
37
|
+
scanFailListeners.values.forEach { listener ->
|
|
38
|
+
try {
|
|
39
|
+
listener(fail)
|
|
40
|
+
} catch (e: Exception) {
|
|
41
|
+
Log.e(TAG, "Error notifying scan fail listener", e)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
package/android/src/main/java/com/margelo/nitro/dawidzawada/bonjourzeroconf/BonjourZeroconf.kt
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
package com.margelo.nitro.dawidzawada.bonjourzeroconf
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.net.nsd.NsdManager
|
|
5
|
+
import android.net.nsd.NsdServiceInfo
|
|
6
|
+
import android.util.Log
|
|
7
|
+
import com.facebook.proguard.annotations.DoNotStrip
|
|
8
|
+
import java.util.UUID
|
|
9
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
10
|
+
import com.margelo.nitro.NitroModules
|
|
11
|
+
import kotlinx.coroutines.CoroutineScope
|
|
12
|
+
import kotlinx.coroutines.Dispatchers
|
|
13
|
+
import kotlinx.coroutines.SupervisorJob
|
|
14
|
+
import kotlinx.coroutines.launch
|
|
15
|
+
import kotlinx.coroutines.sync.Mutex
|
|
16
|
+
|
|
17
|
+
@DoNotStrip
|
|
18
|
+
class BonjourZeroconf : HybridBonjourZeroconfSpec() {
|
|
19
|
+
|
|
20
|
+
companion object {
|
|
21
|
+
internal const val TAG = "BonjourZeroconf"
|
|
22
|
+
internal const val DEFAULT_RESOLVE_TIMEOUT_MS = 10_000L
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@Volatile
|
|
26
|
+
internal var _isScanning = false
|
|
27
|
+
|
|
28
|
+
internal val scanStateListeners = ConcurrentHashMap<UUID, (Boolean) -> Unit>()
|
|
29
|
+
internal val scanResultsListeners = ConcurrentHashMap<UUID, (Array<ScanResult>) -> Unit>()
|
|
30
|
+
internal val scanFailListeners = ConcurrentHashMap<UUID, (BonjourFail) -> Unit>()
|
|
31
|
+
|
|
32
|
+
internal var nsdManager: NsdManager? = null
|
|
33
|
+
internal var currentDiscoveryListener: NsdManager.DiscoveryListener? = null
|
|
34
|
+
internal val serviceCache = ConcurrentHashMap<String, ScanResult>()
|
|
35
|
+
internal val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
|
36
|
+
internal val legacyResolveMutex = Mutex()
|
|
37
|
+
|
|
38
|
+
override val isScanning: Boolean
|
|
39
|
+
get() = _isScanning
|
|
40
|
+
|
|
41
|
+
override fun scan(type: String, domain: String, options: ScanOptions?) {
|
|
42
|
+
if (_isScanning) {
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
val context = NitroModules.applicationContext
|
|
47
|
+
?: throw IllegalStateException("Application context is not available.")
|
|
48
|
+
|
|
49
|
+
if (nsdManager == null) {
|
|
50
|
+
nsdManager = context.getSystemService(Context.NSD_SERVICE) as? NsdManager
|
|
51
|
+
?: throw IllegalStateException("NsdManager service is not available on this device.")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
val resolveTimeout = options?.addressResolveTimeout?.toLong() ?: DEFAULT_RESOLVE_TIMEOUT_MS
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
currentDiscoveryListener = createDiscoveryListener(resolveTimeout).also { listener ->
|
|
58
|
+
try {
|
|
59
|
+
Log.i(TAG, "Starting scan for type: $type")
|
|
60
|
+
updateScanningState(true)
|
|
61
|
+
nsdManager?.discoverServices(type, NsdManager.PROTOCOL_DNS_SD, listener)
|
|
62
|
+
?: throw IllegalStateException("NsdManager is not initialized")
|
|
63
|
+
} catch (e: Exception) {
|
|
64
|
+
Log.e(TAG, "Failed to start discovery", e)
|
|
65
|
+
updateScanningState(false)
|
|
66
|
+
throw RuntimeException("Failed to start service discovery: ${e.message}", e)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
override fun stop() {
|
|
72
|
+
currentDiscoveryListener?.let { listener ->
|
|
73
|
+
try {
|
|
74
|
+
nsdManager?.stopServiceDiscovery(listener)
|
|
75
|
+
Log.i(TAG, "Stopped service discovery")
|
|
76
|
+
} catch (e: Exception) {
|
|
77
|
+
Log.e(TAG, "Error stopping discovery", e)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
currentDiscoveryListener = null
|
|
82
|
+
serviceCache.clear()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
override fun listenForScanResults(onResult: (Array<ScanResult>) -> Unit): BonjourListener {
|
|
86
|
+
val id = UUID.randomUUID()
|
|
87
|
+
scanResultsListeners[id] = onResult
|
|
88
|
+
|
|
89
|
+
val currentResults = serviceCache.values.toTypedArray()
|
|
90
|
+
if (currentResults.isNotEmpty()) {
|
|
91
|
+
scope.launch(Dispatchers.Main) {
|
|
92
|
+
try {
|
|
93
|
+
onResult(currentResults)
|
|
94
|
+
} catch (e: Exception) {
|
|
95
|
+
Log.e(TAG, "Scan results listener error", e)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return BonjourListener {
|
|
101
|
+
scanResultsListeners.remove(id)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
override fun listenForScanState(onChange: (Boolean) -> Unit): BonjourListener {
|
|
106
|
+
val id = UUID.randomUUID()
|
|
107
|
+
scanStateListeners[id] = onChange
|
|
108
|
+
|
|
109
|
+
scope.launch(Dispatchers.Main) {
|
|
110
|
+
try {
|
|
111
|
+
onChange(_isScanning)
|
|
112
|
+
} catch (e: Exception) {
|
|
113
|
+
Log.e(TAG, "Scan state listener error", e)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return BonjourListener {
|
|
118
|
+
scanStateListeners.remove(id)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
override fun listenForScanFail(onFail: (BonjourFail) -> Unit): BonjourListener {
|
|
123
|
+
val id = UUID.randomUUID()
|
|
124
|
+
scanFailListeners[id] = onFail
|
|
125
|
+
|
|
126
|
+
return BonjourListener {
|
|
127
|
+
scanFailListeners.remove(id)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private fun createDiscoveryListener(resolveTimeout: Long) = object : NsdManager.DiscoveryListener {
|
|
132
|
+
|
|
133
|
+
override fun onDiscoveryStarted(serviceType: String) {
|
|
134
|
+
Log.d(TAG, "Discovery started: $serviceType")
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
override fun onServiceFound(service: NsdServiceInfo) {
|
|
138
|
+
val serviceKey = createServiceKey(service)
|
|
139
|
+
Log.d(TAG, "Service found: ${service.serviceName} (key: $serviceKey)")
|
|
140
|
+
|
|
141
|
+
if (serviceCache.containsKey(serviceKey)) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
scope.launch {
|
|
146
|
+
try {
|
|
147
|
+
resolveService(service, serviceKey, resolveTimeout)
|
|
148
|
+
} catch (e: Exception) {
|
|
149
|
+
Log.e(TAG, "Error resolving service: $serviceKey", e)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
override fun onServiceLost(service: NsdServiceInfo) {
|
|
155
|
+
val serviceKey = createServiceKey(service)
|
|
156
|
+
Log.d(TAG, "Service lost: ${service.serviceName} (key: $serviceKey)")
|
|
157
|
+
|
|
158
|
+
serviceCache.remove(serviceKey)?.let {
|
|
159
|
+
notifyScanResultsListeners()
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
override fun onDiscoveryStopped(serviceType: String) {
|
|
164
|
+
Log.d(TAG, "Discovery stopped: $serviceType")
|
|
165
|
+
updateScanningState(false)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
|
|
169
|
+
Log.e(TAG, "Start discovery failed: $serviceType, error: $errorCode")
|
|
170
|
+
notifyScanFailListeners(BonjourFail.DISCOVERY_FAILED)
|
|
171
|
+
updateScanningState(false)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
|
|
175
|
+
notifyScanFailListeners(BonjourFail.DISCOVERY_FAILED)
|
|
176
|
+
Log.e(TAG, "Stop discovery failed: $serviceType, error: $errorCode")
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private fun createServiceKey(service: NsdServiceInfo): String {
|
|
181
|
+
return "${service.serviceName}.${service.serviceType}"
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
package com.margelo.nitro.dawidzawada.bonjourzeroconf
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.TurboReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
7
|
+
|
|
8
|
+
class BonjourZeroconfPackage : TurboReactPackage() {
|
|
9
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
10
|
+
return null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
14
|
+
return ReactModuleInfoProvider { HashMap() }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
companion object {
|
|
18
|
+
init {
|
|
19
|
+
System.loadLibrary("dawidzawada_bonjourzeroconf")
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|