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