@cappitolian/network-discovery 0.0.1

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.
@@ -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 = 'CappitolianNetworkDiscovery'
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 = '14.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: "CappitolianNetworkDiscovery",
6
+ platforms: [.iOS(.v15)],
7
+ products: [
8
+ .library(
9
+ name: "CappitolianNetworkDiscovery",
10
+ targets: ["NetworkDiscoveryPlugin"])
11
+ ],
12
+ dependencies: [
13
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "7.0.0")
14
+ ],
15
+ targets: [
16
+ .target(
17
+ name: "NetworkDiscoveryPlugin",
18
+ dependencies: [
19
+ .product(name: "Capacitor", package: "capacitor-swift-pm"),
20
+ .product(name: "Cordova", package: "capacitor-swift-pm")
21
+ ],
22
+ path: "ios/Sources/NetworkDiscoveryPlugin"),
23
+ .testTarget(
24
+ name: "NetworkDiscoveryPluginTests",
25
+ dependencies: ["NetworkDiscoveryPlugin"],
26
+ path: "ios/Tests/NetworkDiscoveryPluginTests")
27
+ ]
28
+ )
package/README.md ADDED
@@ -0,0 +1,365 @@
1
+ # @cappitolian/network-discovery
2
+
3
+ A Capacitor plugin for network service discovery using mDNS/Bonjour. Allows automatic server-client connection without manual IP configuration.
4
+
5
+ ---
6
+
7
+ ## Features
8
+
9
+ - **Advertise services** on the local network (server mode)
10
+ - **Discover services** automatically (client mode)
11
+ - Pass custom data via TXT records (like IP addresses, ports, etc.)
12
+ - Real-time service found/lost events
13
+ - Works on **iOS** (Bonjour) and **Android** (NSD)
14
+ - Tested with **Capacitor 7** and **Ionic 8**
15
+
16
+ ---
17
+
18
+ ## Installation
19
+ ```bash
20
+ npm install @cappitolian/network-discovery
21
+ npx cap sync
22
+ ```
23
+
24
+ ---
25
+
26
+ ## Usage
27
+
28
+ ### Import
29
+ ```typescript
30
+ import { NetworkDiscovery } from '@cappitolian/network-discovery';
31
+ ```
32
+
33
+ ### Server Mode - Advertise Your Service
34
+ ```typescript
35
+ // Start advertising your server
36
+ await NetworkDiscovery.startAdvertising({
37
+ serviceName: 'MyAppServer',
38
+ serviceType: '_http._tcp', // or '_myapp._tcp' for custom service
39
+ port: 8080,
40
+ txtRecord: {
41
+ ip: '192.168.1.100', // Your server IP
42
+ version: '1.0.0' // Any custom data
43
+ }
44
+ });
45
+
46
+ console.log('Server is now discoverable on the network');
47
+
48
+ // Stop advertising when needed
49
+ await NetworkDiscovery.stopAdvertising();
50
+ ```
51
+
52
+ ### Client Mode - Discover Services
53
+ ```typescript
54
+ // Listen for discovered services
55
+ NetworkDiscovery.addListener('serviceFound', (service) => {
56
+ console.log('Service discovered:', service);
57
+ console.log('Server IP:', service.txtRecord?.ip);
58
+ console.log('Server Port:', service.port);
59
+ console.log('Server Addresses:', service.addresses);
60
+
61
+ // Connect to your server
62
+ connectToServer(service.addresses[0], service.port);
63
+ });
64
+
65
+ // Listen for lost services
66
+ NetworkDiscovery.addListener('serviceLost', (service) => {
67
+ console.log('Service lost:', service.serviceName);
68
+ });
69
+
70
+ // Start discovery
71
+ await NetworkDiscovery.startDiscovery({
72
+ serviceType: '_http._tcp', // Must match the server's serviceType
73
+ domain: 'local.' // Optional, defaults to 'local.'
74
+ });
75
+
76
+ // Stop discovery when needed
77
+ await NetworkDiscovery.stopDiscovery();
78
+
79
+ // Clean up listeners
80
+ await NetworkDiscovery.removeAllListeners();
81
+ ```
82
+
83
+ ### Complete Example - Auto-Connect Flow
84
+
85
+ **Server Side:**
86
+ ```typescript
87
+ import { NetworkDiscovery } from '@cappitolian/network-discovery';
88
+ import { Network } from '@capacitor/network';
89
+
90
+ async startServer() {
91
+ // Get device IP
92
+ const ip = await this.getLocalIP();
93
+
94
+ // Advertise server
95
+ await NetworkDiscovery.startAdvertising({
96
+ serviceName: 'MyAppServer',
97
+ serviceType: '_myapp._tcp',
98
+ port: 8080,
99
+ txtRecord: {
100
+ ip: ip,
101
+ serverName: 'Production Server'
102
+ }
103
+ });
104
+
105
+ console.log('Server advertising on network');
106
+ }
107
+
108
+ async getLocalIP(): Promise<string> {
109
+ const status = await Network.getStatus();
110
+ // Your IP extraction logic here
111
+ return '192.168.1.100';
112
+ }
113
+ ```
114
+
115
+ **Client Side:**
116
+ ```typescript
117
+ import { NetworkDiscovery } from '@cappitolian/network-discovery';
118
+
119
+ async findAndConnectToServer() {
120
+ return new Promise((resolve, reject) => {
121
+
122
+ // Listen for server
123
+ NetworkDiscovery.addListener('serviceFound', async (service) => {
124
+ if (service.serviceName === 'MyAppServer') {
125
+ console.log('Server found!');
126
+
127
+ const serverIP = service.txtRecord?.ip || service.addresses[0];
128
+ const serverPort = service.port;
129
+
130
+ // Stop discovery
131
+ await NetworkDiscovery.stopDiscovery();
132
+ await NetworkDiscovery.removeAllListeners();
133
+
134
+ // Connect to server
135
+ resolve({ ip: serverIP, port: serverPort });
136
+ }
137
+ });
138
+
139
+ // Start discovery
140
+ NetworkDiscovery.startDiscovery({
141
+ serviceType: '_myapp._tcp'
142
+ });
143
+
144
+ // Timeout after 10 seconds
145
+ setTimeout(() => {
146
+ reject(new Error('Server not found'));
147
+ }, 10000);
148
+ });
149
+ }
150
+
151
+ // Usage
152
+ async onLoginClick() {
153
+ try {
154
+ const server = await this.findAndConnectToServer();
155
+ console.log(`Connecting to ${server.ip}:${server.port}`);
156
+ // Your connection logic here
157
+ } catch (error) {
158
+ console.error('Could not find server:', error);
159
+ }
160
+ }
161
+ ```
162
+
163
+ ---
164
+
165
+ ## API Reference
166
+
167
+ ### Methods
168
+
169
+ #### `startAdvertising(options: AdvertisingOptions)`
170
+
171
+ Publishes a service on the local network.
172
+
173
+ **Parameters:**
174
+ - `serviceName` (string): Name of your service (e.g., "MyAppServer")
175
+ - `serviceType` (string): Service type (e.g., "_http._tcp", "_myapp._tcp")
176
+ - `port` (number): Port number your server is listening on
177
+ - `txtRecord` (object, optional): Key-value pairs to broadcast (e.g., IP, version)
178
+
179
+ **Returns:** `Promise<{ success: boolean }>`
180
+
181
+ ---
182
+
183
+ #### `stopAdvertising()`
184
+
185
+ Stops advertising the service.
186
+
187
+ **Returns:** `Promise<{ success: boolean }>`
188
+
189
+ ---
190
+
191
+ #### `startDiscovery(options: DiscoveryOptions)`
192
+
193
+ Starts searching for services on the local network.
194
+
195
+ **Parameters:**
196
+ - `serviceType` (string): Type of service to search for (must match server's type)
197
+ - `domain` (string, optional): Domain to search in (default: "local.")
198
+
199
+ **Returns:** `Promise<void>`
200
+
201
+ ---
202
+
203
+ #### `stopDiscovery()`
204
+
205
+ Stops the service discovery.
206
+
207
+ **Returns:** `Promise<{ success: boolean }>`
208
+
209
+ ---
210
+
211
+ ### Events
212
+
213
+ #### `serviceFound`
214
+
215
+ Fired when a service is discovered.
216
+
217
+ **Payload:**
218
+ ```typescript
219
+ {
220
+ serviceName: string;
221
+ serviceType: string;
222
+ domain: string;
223
+ hostName: string;
224
+ port: number;
225
+ addresses: string[]; // Array of IP addresses
226
+ txtRecord?: { // Custom data from server
227
+ [key: string]: string;
228
+ };
229
+ }
230
+ ```
231
+
232
+ ---
233
+
234
+ #### `serviceLost`
235
+
236
+ Fired when a previously discovered service is no longer available.
237
+
238
+ **Payload:**
239
+ ```typescript
240
+ {
241
+ serviceName: string;
242
+ serviceType: string;
243
+ }
244
+ ```
245
+
246
+ ---
247
+
248
+ ## Service Type Format
249
+
250
+ Service types must follow the format: `_name._protocol`
251
+
252
+ **Common examples:**
253
+ - `_http._tcp` - HTTP service
254
+ - `_https._tcp` - HTTPS service
255
+ - `_myapp._tcp` - Custom app service
256
+ - `_ssh._tcp` - SSH service
257
+
258
+ **Recommended for apps:** Use a custom service type like `_myapp._tcp` to avoid conflicts with other services.
259
+
260
+ ---
261
+
262
+ ## Permissions
263
+
264
+ ### Android
265
+
266
+ Add to `AndroidManifest.xml`:
267
+ ```xml
268
+ <uses-permission android:name="android.permission.INTERNET" />
269
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
270
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
271
+ <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
272
+ ```
273
+
274
+ ### iOS
275
+
276
+ No additional permissions required. Bonjour is enabled by default.
277
+
278
+ Optionally, add to `Info.plist` to declare your service:
279
+ ```xml
280
+ <key>NSBonjourServices</key>
281
+ <array>
282
+ <string>_myapp._tcp</string>
283
+ </array>
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Platforms
289
+
290
+ - **iOS** (Bonjour/NetService)
291
+ - **Android** (Network Service Discovery)
292
+ - **Web** (Not implemented - throws unimplemented error)
293
+
294
+ ---
295
+
296
+ ## Requirements
297
+
298
+ - [Capacitor 7+](https://capacitorjs.com/)
299
+ - [Ionic 8+](https://ionicframework.com/) (optional, but tested)
300
+ - iOS 12.0+
301
+ - Android API 16+ (Android 4.1+)
302
+
303
+ ---
304
+
305
+ ## Troubleshooting
306
+
307
+ ### Services not being discovered
308
+
309
+ 1. **Check both devices are on the same WiFi network**
310
+ 2. **Verify service types match** exactly between server and client
311
+ 3. **Check firewall settings** - some networks block mDNS
312
+ 4. **Android:** Ensure multicast is enabled on your network
313
+ 5. **iOS:** Make sure device is not in Low Power Mode
314
+
315
+ ### Server not advertising
316
+
317
+ 1. **Verify port is not in use** by another service
318
+ 2. **Check network permissions** are granted
319
+ 3. **Restart the app** after installing the plugin
320
+
321
+ ### General debugging
322
+
323
+ Enable verbose logging:
324
+ ```typescript
325
+ // Check plugin is loaded
326
+ console.log('Plugin available:', NetworkDiscovery);
327
+
328
+ // Log all events
329
+ NetworkDiscovery.addListener('serviceFound', (s) => console.log('Found:', s));
330
+ NetworkDiscovery.addListener('serviceLost', (s) => console.log('Lost:', s));
331
+ ```
332
+
333
+ ---
334
+
335
+ ## Use Cases
336
+
337
+ - **Auto-connect apps** - Client automatically finds and connects to server
338
+ - **Local multiplayer games** - Discover game hosts on LAN
339
+ - **IoT device discovery** - Find smart devices without configuration
340
+ - **File sharing apps** - Discover peers for file transfer
341
+ - **Remote control apps** - Find controllable devices automatically
342
+
343
+ ---
344
+
345
+ ## License
346
+
347
+ MIT
348
+
349
+ ---
350
+
351
+ ## Support
352
+
353
+ If you encounter any issues or have feature requests, please open an issue on the [GitHub repository](https://github.com/alessandrycruz1987/network-discovery).
354
+
355
+ ---
356
+
357
+ ## Contributing
358
+
359
+ Contributions are welcome! Please feel free to submit a Pull Request.
360
+
361
+ ---
362
+
363
+ ## Credits
364
+
365
+ Created for use with Capacitor 7 and Ionic 8 applications requiring automatic network service discovery.
@@ -0,0 +1,58 @@
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
+
20
+ android {
21
+ namespace = "com.cappitolian.plugins.networkdiscovery"
22
+ compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
23
+ defaultConfig {
24
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
25
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
26
+ versionCode 1
27
+ versionName "1.0"
28
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
29
+ }
30
+ buildTypes {
31
+ release {
32
+ minifyEnabled false
33
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
34
+ }
35
+ }
36
+ lintOptions {
37
+ abortOnError = false
38
+ }
39
+ compileOptions {
40
+ sourceCompatibility JavaVersion.VERSION_21
41
+ targetCompatibility JavaVersion.VERSION_21
42
+ }
43
+ }
44
+
45
+ repositories {
46
+ google()
47
+ mavenCentral()
48
+ }
49
+
50
+
51
+ dependencies {
52
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
53
+ implementation project(':capacitor-android')
54
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
55
+ testImplementation "junit:junit:$junitVersion"
56
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
57
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
58
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,218 @@
1
+ package com.cappitolian.plugins.networkdiscovery;
2
+
3
+ import android.content.Context;
4
+ import android.net.nsd.NsdManager;
5
+ import android.net.nsd.NsdServiceInfo;
6
+ import android.util.Log;
7
+
8
+ import com.getcapacitor.JSObject;
9
+ import com.getcapacitor.Plugin;
10
+
11
+ import org.json.JSONArray;
12
+ import org.json.JSONException;
13
+
14
+ import java.net.InetAddress;
15
+ import java.util.Map;
16
+
17
+ public class NetworkDiscovery {
18
+ private static final String TAG = "NetworkDiscovery";
19
+ private NsdManager nsdManager;
20
+ private NsdManager.RegistrationListener registrationListener;
21
+ private NsdManager.DiscoveryListener discoveryListener;
22
+ private NsdServiceInfo serviceInfo;
23
+ private Plugin plugin;
24
+
25
+ public NetworkDiscovery(Plugin plugin, Context context) {
26
+ this.plugin = plugin;
27
+ this.nsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
28
+ }
29
+
30
+ public void startAdvertising(
31
+ String serviceName,
32
+ String serviceType,
33
+ int port,
34
+ JSObject txtRecord,
35
+ AdvertisingCallback callback
36
+ ) {
37
+ serviceInfo = new NsdServiceInfo();
38
+ serviceInfo.setServiceName(serviceName);
39
+ serviceInfo.setServiceType(serviceType);
40
+ serviceInfo.setPort(port);
41
+
42
+ // Agregar TXT records
43
+ if (txtRecord != null) {
44
+ for (String key : txtRecord.keys()) {
45
+ try {
46
+ String value = txtRecord.getString(key);
47
+ serviceInfo.setAttribute(key, value);
48
+ } catch (Exception e) {
49
+ Log.e(TAG, "Error setting attribute: " + key, e);
50
+ }
51
+ }
52
+ }
53
+
54
+ registrationListener = new NsdManager.RegistrationListener() {
55
+ @Override
56
+ public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
57
+ Log.e(TAG, "Service registration failed: " + errorCode);
58
+ callback.onError("Registration failed with error code: " + errorCode);
59
+ }
60
+
61
+ @Override
62
+ public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
63
+ Log.e(TAG, "Service unregistration failed: " + errorCode);
64
+ }
65
+
66
+ @Override
67
+ public void onServiceRegistered(NsdServiceInfo serviceInfo) {
68
+ Log.d(TAG, "Service registered: " + serviceInfo.getServiceName());
69
+ callback.onSuccess();
70
+ }
71
+
72
+ @Override
73
+ public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
74
+ Log.d(TAG, "Service unregistered");
75
+ }
76
+ };
77
+
78
+ nsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener);
79
+ }
80
+
81
+ public void stopAdvertising(StopCallback callback) {
82
+ if (registrationListener != null) {
83
+ try {
84
+ nsdManager.unregisterService(registrationListener);
85
+ registrationListener = null;
86
+ callback.onSuccess();
87
+ } catch (Exception e) {
88
+ callback.onError("Error stopping advertising: " + e.getMessage());
89
+ }
90
+ } else {
91
+ callback.onError("No active advertising to stop");
92
+ }
93
+ }
94
+
95
+ public void startDiscovery(String serviceType, DiscoveryCallback callback) {
96
+ discoveryListener = new NsdManager.DiscoveryListener() {
97
+ @Override
98
+ public void onStartDiscoveryFailed(String serviceType, int errorCode) {
99
+ Log.e(TAG, "Discovery start failed: " + errorCode);
100
+ nsdManager.stopServiceDiscovery(this);
101
+ callback.onError("Discovery failed with error code: " + errorCode);
102
+ }
103
+
104
+ @Override
105
+ public void onStopDiscoveryFailed(String serviceType, int errorCode) {
106
+ Log.e(TAG, "Discovery stop failed: " + errorCode);
107
+ }
108
+
109
+ @Override
110
+ public void onDiscoveryStarted(String serviceType) {
111
+ Log.d(TAG, "Service discovery started");
112
+ callback.onDiscoveryStarted();
113
+ }
114
+
115
+ @Override
116
+ public void onDiscoveryStopped(String serviceType) {
117
+ Log.d(TAG, "Service discovery stopped");
118
+ }
119
+
120
+ @Override
121
+ public void onServiceFound(NsdServiceInfo service) {
122
+ Log.d(TAG, "Service found: " + service.getServiceName());
123
+
124
+ nsdManager.resolveService(service, new NsdManager.ResolveListener() {
125
+ @Override
126
+ public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
127
+ Log.e(TAG, "Resolve failed: " + errorCode);
128
+ }
129
+
130
+ @Override
131
+ public void onServiceResolved(NsdServiceInfo serviceInfo) {
132
+ Log.d(TAG, "Service resolved: " + serviceInfo);
133
+
134
+ JSObject serviceData = buildServiceObject(serviceInfo);
135
+ callback.onServiceFound(serviceData);
136
+ }
137
+ });
138
+ }
139
+
140
+ @Override
141
+ public void onServiceLost(NsdServiceInfo service) {
142
+ Log.d(TAG, "Service lost: " + service.getServiceName());
143
+
144
+ JSObject serviceData = new JSObject();
145
+ serviceData.put("serviceName", service.getServiceName());
146
+ serviceData.put("serviceType", service.getServiceType());
147
+
148
+ callback.onServiceLost(serviceData);
149
+ }
150
+ };
151
+
152
+ nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryListener);
153
+ }
154
+
155
+ public void stopDiscovery(StopCallback callback) {
156
+ if (discoveryListener != null) {
157
+ try {
158
+ nsdManager.stopServiceDiscovery(discoveryListener);
159
+ discoveryListener = null;
160
+ callback.onSuccess();
161
+ } catch (Exception e) {
162
+ callback.onError("Error stopping discovery: " + e.getMessage());
163
+ }
164
+ } else {
165
+ callback.onError("No active discovery to stop");
166
+ }
167
+ }
168
+
169
+ private JSObject buildServiceObject(NsdServiceInfo serviceInfo) {
170
+ JSObject serviceData = new JSObject();
171
+ serviceData.put("serviceName", serviceInfo.getServiceName());
172
+ serviceData.put("serviceType", serviceInfo.getServiceType());
173
+ serviceData.put("hostName", serviceInfo.getHost() != null ? serviceInfo.getHost().getHostName() : "");
174
+ serviceData.put("port", serviceInfo.getPort());
175
+
176
+ // Agregar direcciones IP
177
+ InetAddress host = serviceInfo.getHost();
178
+ if (host != null) {
179
+ try {
180
+ JSONArray addresses = new JSONArray();
181
+ addresses.put(host.getHostAddress());
182
+ serviceData.put("addresses", addresses);
183
+ } catch (JSONException e) {
184
+ Log.e(TAG, "Error adding addresses", e);
185
+ }
186
+ }
187
+
188
+ // Agregar TXT records
189
+ Map<String, byte[]> attributes = serviceInfo.getAttributes();
190
+ if (attributes != null && !attributes.isEmpty()) {
191
+ JSObject txtRecordObj = new JSObject();
192
+ for (Map.Entry<String, byte[]> entry : attributes.entrySet()) {
193
+ txtRecordObj.put(entry.getKey(), new String(entry.getValue()));
194
+ }
195
+ serviceData.put("txtRecord", txtRecordObj);
196
+ }
197
+
198
+ return serviceData;
199
+ }
200
+
201
+ // Callbacks interfaces
202
+ public interface AdvertisingCallback {
203
+ void onSuccess();
204
+ void onError(String error);
205
+ }
206
+
207
+ public interface StopCallback {
208
+ void onSuccess();
209
+ void onError(String error);
210
+ }
211
+
212
+ public interface DiscoveryCallback {
213
+ void onDiscoveryStarted();
214
+ void onServiceFound(JSObject service);
215
+ void onServiceLost(JSObject service);
216
+ void onError(String error);
217
+ }
218
+ }