react-native-zeroconf 0.16.1 → 0.17.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.
- package/NOTICE +0 -3
- package/README.md +3 -3
- package/android/build.gradle +0 -3
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroConfImplFactory.java +1 -1
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/Zeroconf.java +12 -2
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroconfModule.java +129 -4
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/ZeroconfOptions.java +77 -0
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/dnssd/DnssdImpl.java +654 -0
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/nsd/NsdServiceImpl.java +393 -39
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/nsd/ServiceInfoCallbacks.java +50 -13
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/nsd/ServiceTypesQuery.java +296 -0
- package/android/src/main/java/com/github/druk/dnssd/DNSSDEmbedded.java +1 -1
- package/android/src/main/jni/mdnsresponder/mDNSShared/dnssd_clientshim.c +59 -22
- package/dist/index.js +235 -48
- package/index.d.ts +105 -7
- package/ios/RNZeroconf/RNZeroconf.m +285 -22
- package/package.json +28 -4
- package/src/index.js +214 -44
- package/windows/ExperimentalFeatures.props +33 -0
- package/windows/NuGet.config +11 -0
- package/windows/RNZeroconf/RNZeroconf.cpp +228 -0
- package/windows/RNZeroconf/RNZeroconf.def +3 -0
- package/windows/RNZeroconf/RNZeroconf.h +70 -0
- package/windows/RNZeroconf/RNZeroconf.rc +0 -0
- package/windows/RNZeroconf/RNZeroconf.vcxproj +150 -0
- package/windows/RNZeroconf/RNZeroconf.vcxproj.filters +50 -0
- package/windows/RNZeroconf/ReactPackageProvider.cpp +20 -0
- package/windows/RNZeroconf/ReactPackageProvider.h +24 -0
- package/windows/RNZeroconf/ReactPackageProvider.idl +9 -0
- package/windows/RNZeroconf/ZeroconfCore.cpp +1095 -0
- package/windows/RNZeroconf/ZeroconfCore.h +167 -0
- package/windows/RNZeroconf/pch.cpp +1 -0
- package/windows/RNZeroconf/pch.h +30 -0
- package/windows/RNZeroconf/resource.h +5 -0
- package/windows/RNZeroconf/targetver.h +8 -0
- package/windows/RNZeroconf.sln +43 -0
- package/android/src/main/java/com/balthazargronon/RCTZeroconf/rx2dnssd/DnssdImpl.java +0 -305
- package/android/src/main/java/com/github/druk/rx2dnssd/BonjourService.java +0 -400
- package/android/src/main/java/com/github/druk/rx2dnssd/Rx2BrowseListener.java +0 -55
- package/android/src/main/java/com/github/druk/rx2dnssd/Rx2Dnssd.java +0 -127
- package/android/src/main/java/com/github/druk/rx2dnssd/Rx2DnssdCommon.java +0 -280
- package/android/src/main/java/com/github/druk/rx2dnssd/Rx2DnssdEmbedded.java +0 -31
- package/android/src/main/java/com/github/druk/rx2dnssd/Rx2QueryListener.java +0 -70
- package/android/src/main/java/com/github/druk/rx2dnssd/Rx2RegisterListener.java +0 -47
- package/android/src/main/java/com/github/druk/rx2dnssd/Rx2ResolveListener.java +0 -51
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.default = exports.ImplType = void 0;
|
|
7
|
+
exports.useServiceTypes = useServiceTypes;
|
|
7
8
|
exports.useZeroconf = useZeroconf;
|
|
8
9
|
var _react = require("react");
|
|
9
10
|
var _reactNative = require("react-native");
|
|
@@ -42,6 +43,12 @@ const withDefaults = (defaults, options) => {
|
|
|
42
43
|
};
|
|
43
44
|
const isOptionsObject = value => value !== null && typeof value === 'object';
|
|
44
45
|
|
|
46
|
+
// Options passed to native code, without the undefined ones
|
|
47
|
+
const nativeOptions = options => Object.fromEntries(Object.entries(options).filter(([, value]) => value !== undefined));
|
|
48
|
+
|
|
49
|
+
// Android 17 (API 37) runtime permission, enforced for apps targeting API 37 or declaring it
|
|
50
|
+
const ACCESS_LOCAL_NETWORK = 'android.permission.ACCESS_LOCAL_NETWORK';
|
|
51
|
+
|
|
45
52
|
// Each instance runs its own scan, identified by this id in native events
|
|
46
53
|
let instanceCount = 0;
|
|
47
54
|
|
|
@@ -97,6 +104,20 @@ const withAddressFamilies = service => {
|
|
|
97
104
|
/**
|
|
98
105
|
* TXT records as ordered [key, value] pairs, from an object or an array of pairs
|
|
99
106
|
*/
|
|
107
|
+
// Browsing this type lists the service types on the network instead of services
|
|
108
|
+
const SERVICE_TYPES = {
|
|
109
|
+
type: 'services._dns-sd',
|
|
110
|
+
protocol: 'udp'
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// "_http._tcp" -> { type: 'http', protocol: 'tcp' }
|
|
114
|
+
const parseServiceType = name => {
|
|
115
|
+
const match = /^_([^.]+)\._(tcp|udp)$/.exec(name);
|
|
116
|
+
return match ? {
|
|
117
|
+
type: match[1],
|
|
118
|
+
protocol: match[2]
|
|
119
|
+
} : null;
|
|
120
|
+
};
|
|
100
121
|
const toTxtPairs = txt => {
|
|
101
122
|
const entries = Array.isArray(txt) ? txt : Object.entries(txt || {});
|
|
102
123
|
return entries.map(([key, value]) => [String(key), String(value)]);
|
|
@@ -108,6 +129,8 @@ class Zeroconf extends _events.EventEmitter {
|
|
|
108
129
|
throw new Error('react-native-zeroconf: native module not found. Make sure the library is linked and the app rebuilt. Expo Go is not supported, use a development build instead.');
|
|
109
130
|
}
|
|
110
131
|
this._services = {};
|
|
132
|
+
this._serviceTypes = {};
|
|
133
|
+
this._scanningTypes = false;
|
|
111
134
|
this._publishedServices = {};
|
|
112
135
|
this._publishedImplTypes = {};
|
|
113
136
|
this._scanImplType = null;
|
|
@@ -146,6 +169,15 @@ class Zeroconf extends _events.EventEmitter {
|
|
|
146
169
|
const {
|
|
147
170
|
name
|
|
148
171
|
} = service;
|
|
172
|
+
if (this._scanningTypes) {
|
|
173
|
+
const serviceType = parseServiceType(name);
|
|
174
|
+
if (serviceType && !this._serviceTypes[name]) {
|
|
175
|
+
this._serviceTypes[name] = serviceType;
|
|
176
|
+
this.emit('typeFound', serviceType);
|
|
177
|
+
this.emit('update');
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
149
181
|
this._services[name] = {
|
|
150
182
|
name
|
|
151
183
|
};
|
|
@@ -159,6 +191,15 @@ class Zeroconf extends _events.EventEmitter {
|
|
|
159
191
|
const {
|
|
160
192
|
name
|
|
161
193
|
} = service;
|
|
194
|
+
if (this._scanningTypes) {
|
|
195
|
+
const serviceType = this._serviceTypes[name];
|
|
196
|
+
if (serviceType) {
|
|
197
|
+
delete this._serviceTypes[name];
|
|
198
|
+
this.emit('typeRemove', serviceType);
|
|
199
|
+
this.emit('update');
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
162
203
|
delete this._services[name];
|
|
163
204
|
this.emit('remove', name);
|
|
164
205
|
this.emit('update');
|
|
@@ -226,6 +267,33 @@ class Zeroconf extends _events.EventEmitter {
|
|
|
226
267
|
return this._services;
|
|
227
268
|
}
|
|
228
269
|
|
|
270
|
+
/**
|
|
271
|
+
* Get the service types found by scanServiceTypes, as [{ type, protocol }]
|
|
272
|
+
*/
|
|
273
|
+
getServiceTypes() {
|
|
274
|
+
return Object.values(this._serviceTypes);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Scan for the service types advertised on the network (_http._tcp, _ipp._tcp...), emits typeFound and typeRemove.
|
|
279
|
+
* On iOS, only types declared in NSBonjourServices can then be scanned.
|
|
280
|
+
* On iOS it requires the multicast entitlement. On Android with NSD and on Windows, services other apps publish on the same device are left out.
|
|
281
|
+
*
|
|
282
|
+
* scanServiceTypes({ domain, implType })
|
|
283
|
+
*/
|
|
284
|
+
scanServiceTypes({
|
|
285
|
+
domain,
|
|
286
|
+
implType,
|
|
287
|
+
networkInterface
|
|
288
|
+
} = {}) {
|
|
289
|
+
this._startScan({
|
|
290
|
+
...SERVICE_TYPES,
|
|
291
|
+
domain,
|
|
292
|
+
implType,
|
|
293
|
+
networkInterface
|
|
294
|
+
}, true);
|
|
295
|
+
}
|
|
296
|
+
|
|
229
297
|
/**
|
|
230
298
|
* Scan for Zeroconf services, defaults to _http._tcp. on the local. domain
|
|
231
299
|
*
|
|
@@ -233,19 +301,26 @@ class Zeroconf extends _events.EventEmitter {
|
|
|
233
301
|
* scan(type, protocol, domain, implType) is deprecated
|
|
234
302
|
*/
|
|
235
303
|
scan(options, protocolArg, domainArg, implTypeArg) {
|
|
304
|
+
this._startScan(isOptionsObject(options) ? options : {
|
|
305
|
+
type: options,
|
|
306
|
+
protocol: protocolArg,
|
|
307
|
+
domain: domainArg,
|
|
308
|
+
implType: implTypeArg
|
|
309
|
+
}, false);
|
|
310
|
+
}
|
|
311
|
+
_startScan(options, scanningTypes) {
|
|
236
312
|
const {
|
|
237
313
|
type,
|
|
238
314
|
protocol,
|
|
239
315
|
domain,
|
|
240
316
|
implType,
|
|
241
|
-
resolveTimeout
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
domain: domainArg,
|
|
246
|
-
implType: implTypeArg
|
|
247
|
-
});
|
|
317
|
+
resolveTimeout,
|
|
318
|
+
subtype,
|
|
319
|
+
networkInterface
|
|
320
|
+
} = withDefaults(SCAN_DEFAULTS, options);
|
|
248
321
|
this._services = {};
|
|
322
|
+
this._serviceTypes = {};
|
|
323
|
+
this._scanningTypes = scanningTypes;
|
|
249
324
|
this._hasScanned = true;
|
|
250
325
|
this.emit('update');
|
|
251
326
|
if (_reactNative.Platform.OS === 'android') {
|
|
@@ -254,9 +329,17 @@ class Zeroconf extends _events.EventEmitter {
|
|
|
254
329
|
RNZeroconf.stop(this._scanId, this._scanImplType);
|
|
255
330
|
}
|
|
256
331
|
this._scanImplType = implType;
|
|
257
|
-
RNZeroconf.scan(this._scanId, type, protocol, domain, implType
|
|
332
|
+
RNZeroconf.scan(this._scanId, type, protocol, domain, implType, nativeOptions({
|
|
333
|
+
resolveTimeout,
|
|
334
|
+
subtype,
|
|
335
|
+
networkInterface
|
|
336
|
+
}));
|
|
258
337
|
} else {
|
|
259
|
-
RNZeroconf.scan(this._scanId, type, protocol, domain,
|
|
338
|
+
RNZeroconf.scan(this._scanId, type, protocol, domain, nativeOptions({
|
|
339
|
+
resolveTimeout,
|
|
340
|
+
subtype,
|
|
341
|
+
networkInterface
|
|
342
|
+
}));
|
|
260
343
|
}
|
|
261
344
|
}
|
|
262
345
|
|
|
@@ -273,17 +356,27 @@ class Zeroconf extends _events.EventEmitter {
|
|
|
273
356
|
}
|
|
274
357
|
|
|
275
358
|
/**
|
|
276
|
-
* Checks the
|
|
277
|
-
*
|
|
278
|
-
* Android
|
|
359
|
+
* Checks the Local Network permission, resolves 'granted', 'denied' or 'unknown'.
|
|
360
|
+
* iOS: uses a service type from NSBonjourServices (the first one by default) and can show the permission prompt.
|
|
361
|
+
* Android: apps targeting Android 17 (API 37) need ACCESS_LOCAL_NETWORK on Android 17 devices, it is requested
|
|
362
|
+
* when missing unless request is false. 'granted' when there is nothing to grant.
|
|
279
363
|
*/
|
|
280
|
-
checkLocalNetworkAccess({
|
|
364
|
+
async checkLocalNetworkAccess({
|
|
281
365
|
type,
|
|
282
366
|
protocol = 'tcp',
|
|
283
|
-
timeout = 5
|
|
367
|
+
timeout = 5,
|
|
368
|
+
request = true
|
|
284
369
|
} = {}) {
|
|
285
370
|
if (_reactNative.Platform.OS === 'android') {
|
|
286
|
-
|
|
371
|
+
if (!RNZeroconf.checkLocalNetworkAccess) {
|
|
372
|
+
return 'unknown';
|
|
373
|
+
}
|
|
374
|
+
const status = await asPromise(RNZeroconf.checkLocalNetworkAccess());
|
|
375
|
+
if (status !== 'denied' || !request) {
|
|
376
|
+
return status;
|
|
377
|
+
}
|
|
378
|
+
const result = await _reactNative.PermissionsAndroid.request(ACCESS_LOCAL_NETWORK);
|
|
379
|
+
return result === _reactNative.PermissionsAndroid.RESULTS.GRANTED ? 'granted' : 'denied';
|
|
287
380
|
}
|
|
288
381
|
const serviceType = type ? `_${type}._${protocol}` : null;
|
|
289
382
|
return asPromise(RNZeroconf.checkLocalNetworkAccess(serviceType, timeout));
|
|
@@ -304,7 +397,9 @@ class Zeroconf extends _events.EventEmitter {
|
|
|
304
397
|
name,
|
|
305
398
|
port,
|
|
306
399
|
txt,
|
|
307
|
-
implType
|
|
400
|
+
implType,
|
|
401
|
+
subtypes,
|
|
402
|
+
networkInterface
|
|
308
403
|
} = withDefaults(PUBLISH_DEFAULTS, isOptionsObject(options) ? options : {
|
|
309
404
|
type: options,
|
|
310
405
|
protocol: protocolArg,
|
|
@@ -315,11 +410,59 @@ class Zeroconf extends _events.EventEmitter {
|
|
|
315
410
|
implType: implTypeArg
|
|
316
411
|
});
|
|
317
412
|
const txtRecord = toTxtPairs(txt);
|
|
413
|
+
const native = nativeOptions({
|
|
414
|
+
subtypes,
|
|
415
|
+
networkInterface
|
|
416
|
+
});
|
|
318
417
|
if (_reactNative.Platform.OS === 'android') {
|
|
319
418
|
this._publishedImplTypes[name] = implType;
|
|
320
|
-
return asPromise(RNZeroconf.registerService(type, protocol, domain, name, port, txtRecord, implType));
|
|
419
|
+
return asPromise(RNZeroconf.registerService(type, protocol, domain, name, port, txtRecord, implType, native));
|
|
420
|
+
}
|
|
421
|
+
return asPromise(RNZeroconf.registerService(type, protocol, domain, name, port, txtRecord, native));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Replace the TXT record of a published service, resolves with the updated service.
|
|
426
|
+
* Android NSD has no update, the service is published again under the same name.
|
|
427
|
+
*
|
|
428
|
+
* updateService(name, { txt })
|
|
429
|
+
*/
|
|
430
|
+
updateService(name, {
|
|
431
|
+
txt = {},
|
|
432
|
+
implType = this._publishedImplTypes[name] || ImplType.NSD
|
|
433
|
+
} = {}) {
|
|
434
|
+
const txtRecord = toTxtPairs(txt);
|
|
435
|
+
if (_reactNative.Platform.OS === 'android') {
|
|
436
|
+
return asPromise(RNZeroconf.updateService(name, txtRecord, implType));
|
|
321
437
|
}
|
|
322
|
-
return asPromise(RNZeroconf.
|
|
438
|
+
return asPromise(RNZeroconf.updateService(name, txtRecord));
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Resolve one service by name without scanning, for a device found before.
|
|
443
|
+
* Resolves with the service, rejects with code 'TIMEOUT' when it doesn't answer in time.
|
|
444
|
+
*
|
|
445
|
+
* resolveService({ name, type, protocol, domain, implType, timeout, networkInterface })
|
|
446
|
+
*/
|
|
447
|
+
resolveService(options) {
|
|
448
|
+
const {
|
|
449
|
+
name,
|
|
450
|
+
type,
|
|
451
|
+
protocol,
|
|
452
|
+
domain,
|
|
453
|
+
implType,
|
|
454
|
+
timeout,
|
|
455
|
+
networkInterface
|
|
456
|
+
} = withDefaults({
|
|
457
|
+
...SCAN_DEFAULTS,
|
|
458
|
+
timeout: 5
|
|
459
|
+
}, options);
|
|
460
|
+
const native = nativeOptions({
|
|
461
|
+
timeout,
|
|
462
|
+
networkInterface
|
|
463
|
+
});
|
|
464
|
+
const promise = _reactNative.Platform.OS === 'android' ? RNZeroconf.resolveService(name, type, protocol, domain, implType, native) : RNZeroconf.resolveService(name, type, protocol, domain, native);
|
|
465
|
+
return asPromise(asPromise(promise).then(service => service && withAddressFamilies(service)));
|
|
323
466
|
}
|
|
324
467
|
|
|
325
468
|
/**
|
|
@@ -335,34 +478,20 @@ class Zeroconf extends _events.EventEmitter {
|
|
|
335
478
|
}
|
|
336
479
|
}
|
|
337
480
|
|
|
338
|
-
|
|
339
|
-
* Scans while mounted and returns the resolved services.
|
|
340
|
-
* Scans again when the options change, stops and cleans up on unmount.
|
|
341
|
-
* Each hook runs its own scan, several can run at once.
|
|
342
|
-
*
|
|
343
|
-
* const { services, isScanning, error, stop, restart } = useZeroconf({ type: 'http' })
|
|
344
|
-
*/
|
|
481
|
+
// Runs a scan while mounted and enabled, and again when deps change or on restart
|
|
345
482
|
exports.default = Zeroconf;
|
|
346
|
-
function
|
|
347
|
-
const {
|
|
348
|
-
type,
|
|
349
|
-
protocol,
|
|
350
|
-
domain,
|
|
351
|
-
implType,
|
|
352
|
-
resolveTimeout,
|
|
353
|
-
enabled = true
|
|
354
|
-
} = options;
|
|
483
|
+
function useScan(enabled, startScan, read, deps) {
|
|
355
484
|
const zeroconfRef = (0, _react.useRef)(null);
|
|
356
|
-
const [
|
|
485
|
+
const [items, setItems] = (0, _react.useState)([]);
|
|
357
486
|
const [isScanning, setIsScanning] = (0, _react.useState)(false);
|
|
358
487
|
const [error, setError] = (0, _react.useState)(null);
|
|
359
488
|
const [scanCount, setScanCount] = (0, _react.useState)(0);
|
|
489
|
+
const readRef = (0, _react.useRef)(read);
|
|
490
|
+
readRef.current = read;
|
|
360
491
|
(0, _react.useEffect)(() => {
|
|
361
492
|
const zeroconf = new Zeroconf();
|
|
362
493
|
zeroconfRef.current = zeroconf;
|
|
363
|
-
|
|
364
|
-
const updateServices = () => setServices(Object.values(zeroconf.getServices()).filter(service => service.addresses));
|
|
365
|
-
const unsubscribes = [zeroconf.subscribe('start', () => setIsScanning(true)), zeroconf.subscribe('stop', () => setIsScanning(false)), zeroconf.subscribe('update', updateServices), zeroconf.subscribe('error', setError)];
|
|
494
|
+
const unsubscribes = [zeroconf.subscribe('start', () => setIsScanning(true)), zeroconf.subscribe('stop', () => setIsScanning(false)), zeroconf.subscribe('update', () => setItems(readRef.current(zeroconf))), zeroconf.subscribe('error', setError)];
|
|
366
495
|
return () => {
|
|
367
496
|
unsubscribes.forEach(unsubscribe => unsubscribe());
|
|
368
497
|
zeroconf.stop();
|
|
@@ -375,17 +504,11 @@ function useZeroconf(options = {}) {
|
|
|
375
504
|
if (!zeroconf || !enabled) {
|
|
376
505
|
return undefined;
|
|
377
506
|
}
|
|
378
|
-
|
|
507
|
+
setItems([]);
|
|
379
508
|
setError(null);
|
|
380
|
-
zeroconf
|
|
381
|
-
type,
|
|
382
|
-
protocol,
|
|
383
|
-
domain,
|
|
384
|
-
implType,
|
|
385
|
-
resolveTimeout
|
|
386
|
-
});
|
|
509
|
+
startScan(zeroconf);
|
|
387
510
|
return () => zeroconf.stop();
|
|
388
|
-
}, [enabled,
|
|
511
|
+
}, [enabled, scanCount, ...deps]);
|
|
389
512
|
const stop = (0, _react.useCallback)(() => {
|
|
390
513
|
if (zeroconfRef.current) {
|
|
391
514
|
zeroconfRef.current.stop();
|
|
@@ -393,10 +516,74 @@ function useZeroconf(options = {}) {
|
|
|
393
516
|
}, []);
|
|
394
517
|
const restart = (0, _react.useCallback)(() => setScanCount(count => count + 1), []);
|
|
395
518
|
return {
|
|
396
|
-
|
|
519
|
+
items,
|
|
397
520
|
isScanning,
|
|
398
521
|
error,
|
|
399
522
|
stop,
|
|
400
523
|
restart
|
|
401
524
|
};
|
|
402
525
|
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Scans while mounted and returns the resolved services.
|
|
529
|
+
* Scans again when the options change, stops and cleans up on unmount.
|
|
530
|
+
* Each hook runs its own scan, several can run at once.
|
|
531
|
+
*
|
|
532
|
+
* const { services, isScanning, error, stop, restart } = useZeroconf({ type: 'http' })
|
|
533
|
+
*/
|
|
534
|
+
function useZeroconf(options = {}) {
|
|
535
|
+
const {
|
|
536
|
+
type,
|
|
537
|
+
protocol,
|
|
538
|
+
domain,
|
|
539
|
+
implType,
|
|
540
|
+
resolveTimeout,
|
|
541
|
+
subtype,
|
|
542
|
+
networkInterface,
|
|
543
|
+
enabled = true
|
|
544
|
+
} = options;
|
|
545
|
+
const {
|
|
546
|
+
items,
|
|
547
|
+
...scan
|
|
548
|
+
} = useScan(enabled, zeroconf => zeroconf.scan({
|
|
549
|
+
type,
|
|
550
|
+
protocol,
|
|
551
|
+
domain,
|
|
552
|
+
implType,
|
|
553
|
+
resolveTimeout,
|
|
554
|
+
subtype,
|
|
555
|
+
networkInterface
|
|
556
|
+
}),
|
|
557
|
+
// Found services only have a name until they are resolved
|
|
558
|
+
zeroconf => Object.values(zeroconf.getServices()).filter(service => service.addresses), [type, protocol, domain, implType, resolveTimeout, subtype, networkInterface]);
|
|
559
|
+
return {
|
|
560
|
+
services: items,
|
|
561
|
+
...scan
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Lists the service types advertised on the network while mounted.
|
|
567
|
+
*
|
|
568
|
+
* const { serviceTypes, isScanning, error, stop, restart } = useServiceTypes()
|
|
569
|
+
*/
|
|
570
|
+
function useServiceTypes(options = {}) {
|
|
571
|
+
const {
|
|
572
|
+
domain,
|
|
573
|
+
implType,
|
|
574
|
+
networkInterface,
|
|
575
|
+
enabled = true
|
|
576
|
+
} = options;
|
|
577
|
+
const {
|
|
578
|
+
items,
|
|
579
|
+
...scan
|
|
580
|
+
} = useScan(enabled, zeroconf => zeroconf.scanServiceTypes({
|
|
581
|
+
domain,
|
|
582
|
+
implType,
|
|
583
|
+
networkInterface
|
|
584
|
+
}), zeroconf => zeroconf.getServiceTypes(), [domain, implType, networkInterface]);
|
|
585
|
+
return {
|
|
586
|
+
serviceTypes: items,
|
|
587
|
+
...scan
|
|
588
|
+
};
|
|
589
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -36,8 +36,18 @@ export interface ScanOptions {
|
|
|
36
36
|
domain?: string
|
|
37
37
|
/** Android only, defaults to `NSD` */
|
|
38
38
|
implType?: ImplType
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Seconds to try resolving each service before retrying once, then emitting an `error` with code `'TIMEOUT'`.
|
|
41
|
+
* Defaults to `5`. Android `NSD` before Android 14 can't retry a resolve, the error comes after twice this time
|
|
42
|
+
*/
|
|
40
43
|
resolveTimeout?: number
|
|
44
|
+
/** Only find services registered with this subtype, e.g. `'printer'` */
|
|
45
|
+
subtype?: string
|
|
46
|
+
/**
|
|
47
|
+
* Network interface to scan on, e.g. `'en0'` on iOS or `'wlan0'` on Android. All of them by default.
|
|
48
|
+
* Android `NSD` needs Android 13 or later for it
|
|
49
|
+
*/
|
|
50
|
+
networkInterface?: string
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
export interface PublishOptions {
|
|
@@ -53,6 +63,34 @@ export interface PublishOptions {
|
|
|
53
63
|
txt?: TxtRecord
|
|
54
64
|
/** Android only, defaults to `NSD` */
|
|
55
65
|
implType?: ImplType
|
|
66
|
+
/** Subtypes to register the service with, e.g. `['printer']` */
|
|
67
|
+
subtypes?: string[]
|
|
68
|
+
/** Network interface to publish on, all of them by default. Android `NSD` needs Android 13 or later for it */
|
|
69
|
+
networkInterface?: string
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface UpdateOptions {
|
|
73
|
+
/** The new TXT record, replaces the current one */
|
|
74
|
+
txt?: TxtRecord
|
|
75
|
+
/** Android only, defaults to the implementation the service was published with */
|
|
76
|
+
implType?: ImplType
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface ResolveOptions {
|
|
80
|
+
/** Name of the service */
|
|
81
|
+
name: string
|
|
82
|
+
/** Service type without underscore, e.g. `'http'`. Defaults to `'http'` */
|
|
83
|
+
type?: string
|
|
84
|
+
/** Defaults to `'tcp'` */
|
|
85
|
+
protocol?: string
|
|
86
|
+
/** Defaults to `'local.'` */
|
|
87
|
+
domain?: string
|
|
88
|
+
/** Android only, defaults to `NSD` */
|
|
89
|
+
implType?: ImplType
|
|
90
|
+
/** Seconds before rejecting with code `'TIMEOUT'`, defaults to `5` */
|
|
91
|
+
timeout?: number
|
|
92
|
+
/** Network interface to resolve on, all of them by default */
|
|
93
|
+
networkInterface?: string
|
|
56
94
|
}
|
|
57
95
|
|
|
58
96
|
/**
|
|
@@ -61,11 +99,12 @@ export interface PublishOptions {
|
|
|
61
99
|
* - `DNSSD`: DNSServiceErrorType, on iOS and from Android's embedded mDNSResponder,
|
|
62
100
|
* e.g. `-65555` type missing from NSBonjourServices, `-65570` Local Network access denied
|
|
63
101
|
* - `NsdManager`: Android NsdManager failure code
|
|
64
|
-
* - `
|
|
102
|
+
* - `Windows`: Win32 / DNS_STATUS code from the Windows DNS-SD functions
|
|
103
|
+
* - `RNZeroconf`: the library's own errors, e.g. `'EXCEPTION'`, `'TIMEOUT'`, `'UNKNOWN_INTERFACE'`
|
|
65
104
|
*/
|
|
66
105
|
export interface ZeroconfError extends Error {
|
|
67
106
|
code: number | string
|
|
68
|
-
domain: 'DNSSD' | 'NsdManager' | 'RNZeroconf'
|
|
107
|
+
domain: 'DNSSD' | 'NsdManager' | 'RNZeroconf' | 'Windows'
|
|
69
108
|
/** The service the error is about, when there is one */
|
|
70
109
|
serviceName?: string
|
|
71
110
|
}
|
|
@@ -75,8 +114,10 @@ export interface LocalNetworkAccessOptions {
|
|
|
75
114
|
type?: string
|
|
76
115
|
/** Defaults to `'tcp'` */
|
|
77
116
|
protocol?: string
|
|
78
|
-
/**
|
|
117
|
+
/** iOS: seconds to wait for an answer, defaults to `5` */
|
|
79
118
|
timeout?: number
|
|
119
|
+
/** Android: request `ACCESS_LOCAL_NETWORK` when it is missing, defaults to `true` */
|
|
120
|
+
request?: boolean
|
|
80
121
|
}
|
|
81
122
|
|
|
82
123
|
/** Published services don't carry the address family fields */
|
|
@@ -91,13 +132,32 @@ export interface ZeroconfEvents {
|
|
|
91
132
|
resolved: (service: Service) => void
|
|
92
133
|
/** Service removed from the network */
|
|
93
134
|
remove: (name: string) => void
|
|
94
|
-
/** Services list changed */
|
|
135
|
+
/** Services or service types list changed */
|
|
95
136
|
update: () => void
|
|
137
|
+
/** Service type found by `scanServiceTypes` */
|
|
138
|
+
typeFound: (serviceType: ServiceType) => void
|
|
139
|
+
/** Service type no longer advertised, during `scanServiceTypes` */
|
|
140
|
+
typeRemove: (serviceType: ServiceType) => void
|
|
96
141
|
error: (error: ZeroconfError) => void
|
|
97
142
|
published: (service: PublishedService) => void
|
|
98
143
|
unpublished: (service: PublishedService) => void
|
|
99
144
|
}
|
|
100
145
|
|
|
146
|
+
/** A service type advertised on the network, e.g. `{ type: 'http', protocol: 'tcp' }` for `_http._tcp` */
|
|
147
|
+
export interface ServiceType {
|
|
148
|
+
type: string
|
|
149
|
+
protocol: 'tcp' | 'udp'
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface ServiceTypesScanOptions {
|
|
153
|
+
/** Defaults to `local.` */
|
|
154
|
+
domain?: string
|
|
155
|
+
/** Android only, defaults to `ImplType.NSD`. With `NSD`, services other apps publish on the same phone are left out (this app's own are included), `DNSSD` includes them */
|
|
156
|
+
implType?: ImplType
|
|
157
|
+
/** Network interface to list the types on, all of them by default */
|
|
158
|
+
networkInterface?: string
|
|
159
|
+
}
|
|
160
|
+
|
|
101
161
|
/** Extends the `events` package EventEmitter */
|
|
102
162
|
export default class Zeroconf {
|
|
103
163
|
constructor()
|
|
@@ -121,14 +181,24 @@ export default class Zeroconf {
|
|
|
121
181
|
/** All the services found so far, keyed by name */
|
|
122
182
|
getServices(): Record<string, Service>
|
|
123
183
|
|
|
184
|
+
/** Service types found by `scanServiceTypes` */
|
|
185
|
+
getServiceTypes(): ServiceType[]
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Scan for the service types advertised on the network, emits `typeFound` and `typeRemove`.
|
|
189
|
+
* On iOS it requires the `com.apple.developer.networking.multicast` entitlement.
|
|
190
|
+
*/
|
|
191
|
+
scanServiceTypes(options?: ServiceTypesScanOptions): void
|
|
192
|
+
|
|
124
193
|
/** Scan for services, defaults to `_http._tcp.` on the `local.` domain */
|
|
125
194
|
scan(options?: ScanOptions): void
|
|
126
195
|
/** @deprecated Use `scan({ type, protocol, domain, implType })` */
|
|
127
196
|
scan(type?: string, protocol?: string, domain?: string, implType?: ImplType): void
|
|
128
197
|
|
|
129
198
|
/**
|
|
130
|
-
* Checks the
|
|
131
|
-
*
|
|
199
|
+
* Checks the Local Network permission. iOS can show the permission prompt when it hasn't been answered yet.
|
|
200
|
+
* Android 17 (API 37): apps targeting API 37 need `ACCESS_LOCAL_NETWORK`, requested when missing unless
|
|
201
|
+
* `request` is `false`. Resolves `'granted'` when there is nothing to grant.
|
|
132
202
|
*/
|
|
133
203
|
checkLocalNetworkAccess(
|
|
134
204
|
options?: LocalNetworkAccessOptions,
|
|
@@ -162,6 +232,15 @@ export default class Zeroconf {
|
|
|
162
232
|
* @param implType Android only, defaults to the implementation the service was published with
|
|
163
233
|
*/
|
|
164
234
|
unpublishService(name: string, implType?: ImplType): Promise<PublishedService | null>
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Replace the TXT record of a published service, resolves with the updated service.
|
|
238
|
+
* Android `NSD` has no update: the service is published again under the same name.
|
|
239
|
+
*/
|
|
240
|
+
updateService(name: string, options: UpdateOptions): Promise<PublishedService>
|
|
241
|
+
|
|
242
|
+
/** Resolve one service by name without scanning. Rejects with code `'TIMEOUT'` when it doesn't answer in time */
|
|
243
|
+
resolveService(options: ResolveOptions): Promise<Service>
|
|
165
244
|
}
|
|
166
245
|
|
|
167
246
|
export interface UseZeroconfOptions extends ScanOptions {
|
|
@@ -185,3 +264,22 @@ export interface UseZeroconfResult {
|
|
|
185
264
|
* stops and cleans up on unmount. Each hook runs its own scan, several can run at once.
|
|
186
265
|
*/
|
|
187
266
|
export function useZeroconf(options?: UseZeroconfOptions): UseZeroconfResult
|
|
267
|
+
|
|
268
|
+
export interface UseServiceTypesOptions extends ServiceTypesScanOptions {
|
|
269
|
+
/** Scan only while true, defaults to `true` */
|
|
270
|
+
enabled?: boolean
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface UseServiceTypesResult {
|
|
274
|
+
/** Service types advertised on the network, updated as they appear and disappear */
|
|
275
|
+
serviceTypes: ServiceType[]
|
|
276
|
+
isScanning: boolean
|
|
277
|
+
/** Last error, reset when a new scan starts */
|
|
278
|
+
error: ZeroconfError | null
|
|
279
|
+
stop(): void
|
|
280
|
+
/** Clears the service types and scans again */
|
|
281
|
+
restart(): void
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Lists the service types advertised on the network while mounted */
|
|
285
|
+
export function useServiceTypes(options?: UseServiceTypesOptions): UseServiceTypesResult
|