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
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
#import "RNNetServiceSerializer.h"
|
|
11
11
|
#import <Network/Network.h>
|
|
12
12
|
#import <dns_sd.h>
|
|
13
|
+
#import <net/if.h>
|
|
13
14
|
|
|
14
15
|
// dns_sd callbacks are delivered on the main queue, and refs must be deallocated on it
|
|
15
16
|
#define RNZ_QUEUE dispatch_get_main_queue()
|
|
@@ -18,10 +19,19 @@
|
|
|
18
19
|
|
|
19
20
|
@class RNZScan;
|
|
20
21
|
|
|
21
|
-
// A found service being resolved: DNSServiceResolve for host, port and TXT, then DNSServiceGetAddrInfo for addresses
|
|
22
|
+
// A found service being resolved: DNSServiceResolve for host, port and TXT, then DNSServiceGetAddrInfo for addresses.
|
|
23
|
+
// Once resolved, the address query and a TXT record query stay open, and changes are emitted again.
|
|
24
|
+
// Without a scan, it is a single resolveService() call settling a promise.
|
|
22
25
|
@interface RNZResolve : NSObject
|
|
23
26
|
@property (nonatomic, weak) RNZeroconf *module;
|
|
24
27
|
@property (nonatomic, weak) RNZScan *scan;
|
|
28
|
+
@property (nonatomic, copy) RCTPromiseResolveBlock promiseResolve;
|
|
29
|
+
@property (nonatomic, copy) RCTPromiseRejectBlock promiseReject;
|
|
30
|
+
@property (nonatomic, assign) uint32_t interfaceIndex;
|
|
31
|
+
@property (nonatomic, assign) NSTimeInterval timeoutSeconds;
|
|
32
|
+
@property (nonatomic, assign) DNSServiceRef txtRef;
|
|
33
|
+
@property (nonatomic, assign) BOOL emitted;
|
|
34
|
+
@property (nonatomic, strong) NSDictionary *lastEmitted;
|
|
25
35
|
@property (nonatomic, copy) NSString *name;
|
|
26
36
|
@property (nonatomic, copy) NSString *regtype;
|
|
27
37
|
@property (nonatomic, copy) NSString *domain;
|
|
@@ -46,9 +56,12 @@
|
|
|
46
56
|
@property (nonatomic, copy) NSString *scanId;
|
|
47
57
|
@property (nonatomic, assign) DNSServiceRef browseRef;
|
|
48
58
|
@property (nonatomic, assign) NSTimeInterval resolveTimeoutSeconds;
|
|
59
|
+
@property (nonatomic, assign) uint32_t interfaceIndex;
|
|
49
60
|
// Services are reported once per network interface, count them so found/remove are emitted once
|
|
50
61
|
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *foundInterfaces;
|
|
51
62
|
@property (nonatomic, strong) NSMutableDictionary<NSString *, RNZResolve *> *resolvingServices;
|
|
63
|
+
// Browsing _services._dns-sd._udp: results are service types, reported as "_http._tcp" and not resolved
|
|
64
|
+
@property (nonatomic, assign) BOOL typesOnly;
|
|
52
65
|
@end
|
|
53
66
|
|
|
54
67
|
@implementation RNZScan
|
|
@@ -77,10 +90,13 @@
|
|
|
77
90
|
@property (nonatomic, strong, readonly) NSMutableDictionary<NSString *, RNZPublication *> *publishedServices;
|
|
78
91
|
// Waiting for the register callback
|
|
79
92
|
@property (nonatomic, strong, readonly) NSMutableSet<RNZPublication *> *pendingPublications;
|
|
93
|
+
// resolveService() calls in flight
|
|
94
|
+
@property (nonatomic, strong, readonly) NSMutableSet<RNZResolve *> *singleResolves;
|
|
80
95
|
|
|
81
96
|
- (void) handleBrowse:(RNZScan *)scan flags:(DNSServiceFlags)flags error:(DNSServiceErrorType)error name:(const char *)name regtype:(const char *)regtype domain:(const char *)domain;
|
|
82
97
|
- (void) handleResolve:(RNZResolve *)resolve error:(DNSServiceErrorType)error host:(const char *)host port:(uint16_t)port txtLength:(uint16_t)txtLength txt:(const unsigned char *)txt;
|
|
83
98
|
- (void) handleAddress:(RNZResolve *)resolve flags:(DNSServiceFlags)flags error:(DNSServiceErrorType)error address:(const struct sockaddr *)address;
|
|
99
|
+
- (void) handleTXT:(RNZResolve *)resolve flags:(DNSServiceFlags)flags error:(DNSServiceErrorType)error length:(uint16_t)length data:(const void *)data;
|
|
84
100
|
- (void) handleRegister:(RNZPublication *)publication flags:(DNSServiceFlags)flags error:(DNSServiceErrorType)error name:(const char *)name;
|
|
85
101
|
|
|
86
102
|
@end
|
|
@@ -109,6 +125,14 @@ static void RNZAddressReply(DNSServiceRef ref, DNSServiceFlags flags, uint32_t i
|
|
|
109
125
|
[resolve.module handleAddress:resolve flags:flags error:error address:address];
|
|
110
126
|
}
|
|
111
127
|
|
|
128
|
+
static void RNZTXTReply(DNSServiceRef ref, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType error,
|
|
129
|
+
const char *fullName, uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata,
|
|
130
|
+
uint32_t ttl, void *context)
|
|
131
|
+
{
|
|
132
|
+
RNZResolve *resolve = (__bridge RNZResolve *)context;
|
|
133
|
+
[resolve.module handleTXT:resolve flags:flags error:error length:rdlen data:rdata];
|
|
134
|
+
}
|
|
135
|
+
|
|
112
136
|
static void RNZRegisterReply(DNSServiceRef ref, DNSServiceFlags flags, DNSServiceErrorType error,
|
|
113
137
|
const char *name, const char *regtype, const char *domain, void *context)
|
|
114
138
|
{
|
|
@@ -153,6 +177,7 @@ RCT_EXPORT_MODULE()
|
|
|
153
177
|
_scans = [[NSMutableDictionary alloc] init];
|
|
154
178
|
_publishedServices = [[NSMutableDictionary alloc] init];
|
|
155
179
|
_pendingPublications = [[NSMutableSet alloc] init];
|
|
180
|
+
_singleResolves = [[NSMutableSet alloc] init];
|
|
156
181
|
}
|
|
157
182
|
|
|
158
183
|
return self;
|
|
@@ -160,28 +185,45 @@ RCT_EXPORT_MODULE()
|
|
|
160
185
|
|
|
161
186
|
#pragma mark - Scan
|
|
162
187
|
|
|
188
|
+
// options: resolveTimeout (seconds), subtype, networkInterface
|
|
163
189
|
RCT_EXPORT_METHOD(scan:(NSString *)scanId
|
|
164
190
|
type:(NSString *)type
|
|
165
191
|
protocol:(NSString *)protocol
|
|
166
192
|
domain:(NSString *)domain
|
|
167
|
-
|
|
193
|
+
options:(NSDictionary *)options)
|
|
168
194
|
{
|
|
169
195
|
[self stopScan:scanId];
|
|
170
196
|
|
|
171
197
|
RNZScan *scan = [[RNZScan alloc] init];
|
|
172
198
|
scan.module = self;
|
|
173
199
|
scan.scanId = scanId;
|
|
200
|
+
double resolveTimeout = [options[@"resolveTimeout"] respondsToSelector:@selector(doubleValue)] ? [options[@"resolveTimeout"] doubleValue] : 0;
|
|
174
201
|
scan.resolveTimeoutSeconds = resolveTimeout > 0 ? resolveTimeout : 5.0;
|
|
175
202
|
scan.foundInterfaces = [[NSMutableDictionary alloc] init];
|
|
176
203
|
scan.resolvingServices = [[NSMutableDictionary alloc] init];
|
|
177
204
|
|
|
205
|
+
uint32_t interfaceIndex = kDNSServiceInterfaceIndexAny;
|
|
206
|
+
NSDictionary *interfaceError = [self interfaceIndexForName:options[@"networkInterface"] index:&interfaceIndex];
|
|
207
|
+
if (interfaceError) {
|
|
208
|
+
[self sendError:interfaceError scan:scan];
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
scan.interfaceIndex = interfaceIndex;
|
|
212
|
+
|
|
178
213
|
NSString *regtype = [NSString stringWithFormat:@"_%@._%@", type, protocol];
|
|
214
|
+
scan.typesOnly = [regtype isEqualToString:@"_services._dns-sd._udp"];
|
|
215
|
+
// "_ipp._tcp,_printer" browses the _printer subtype
|
|
216
|
+
NSString *browseType = regtype;
|
|
217
|
+
NSString *subtype = [RNZeroconf subtypeLabel:options[@"subtype"]];
|
|
218
|
+
if (subtype) {
|
|
219
|
+
browseType = [NSString stringWithFormat:@"%@,%@", regtype, subtype];
|
|
220
|
+
}
|
|
179
221
|
DNSServiceRef ref = NULL;
|
|
180
|
-
DNSServiceErrorType error = DNSServiceBrowse(&ref, 0,
|
|
222
|
+
DNSServiceErrorType error = DNSServiceBrowse(&ref, 0, interfaceIndex, browseType.UTF8String,
|
|
181
223
|
domain.length > 0 ? domain.UTF8String : NULL,
|
|
182
224
|
RNZBrowseReply, (__bridge void *)scan);
|
|
183
225
|
if (error != kDNSServiceErr_NoError) {
|
|
184
|
-
[self sendError:[self
|
|
226
|
+
[self sendError:[self browseErrorWithCode:error scan:scan] scan:scan];
|
|
185
227
|
return;
|
|
186
228
|
}
|
|
187
229
|
DNSServiceSetDispatchQueue(ref, RNZ_QUEUE);
|
|
@@ -229,7 +271,7 @@ RCT_EXPORT_METHOD(stop:(NSString *)scanId)
|
|
|
229
271
|
return;
|
|
230
272
|
}
|
|
231
273
|
if (error != kDNSServiceErr_NoError) {
|
|
232
|
-
[self sendError:[self
|
|
274
|
+
[self sendError:[self browseErrorWithCode:error scan:scan] scan:scan];
|
|
233
275
|
// The browse can't continue after an error
|
|
234
276
|
RNZDeallocateLater(scan.browseRef, scan);
|
|
235
277
|
scan.browseRef = NULL;
|
|
@@ -243,12 +285,20 @@ RCT_EXPORT_METHOD(stop:(NSString *)scanId)
|
|
|
243
285
|
if (!serviceName) {
|
|
244
286
|
return;
|
|
245
287
|
}
|
|
288
|
+
if (scan.typesOnly) {
|
|
289
|
+
// name is "_http" and regtype "_tcp.local.", the service type is "_http._tcp"
|
|
290
|
+
NSString *protocol = [[NSString stringWithUTF8String:regtype] componentsSeparatedByString:@"."].firstObject;
|
|
291
|
+
serviceName = [NSString stringWithFormat:@"%@.%@", serviceName, protocol];
|
|
292
|
+
}
|
|
246
293
|
NSInteger interfaces = scan.foundInterfaces[serviceName].integerValue;
|
|
247
294
|
|
|
248
295
|
if (flags & kDNSServiceFlagsAdd) {
|
|
249
296
|
scan.foundInterfaces[serviceName] = @(interfaces + 1);
|
|
250
297
|
if (interfaces == 0) {
|
|
251
298
|
[self sendEvent:@"RNZeroconfFound" scan:scan body:@{ kRNServiceKeysName: serviceName }];
|
|
299
|
+
if (scan.typesOnly) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
252
302
|
[self startResolve:serviceName regtype:[NSString stringWithUTF8String:regtype] domain:[NSString stringWithUTF8String:domain] scan:scan];
|
|
253
303
|
}
|
|
254
304
|
return;
|
|
@@ -279,6 +329,8 @@ RCT_EXPORT_METHOD(stop:(NSString *)scanId)
|
|
|
279
329
|
resolve.name = name;
|
|
280
330
|
resolve.regtype = regtype;
|
|
281
331
|
resolve.domain = domain;
|
|
332
|
+
resolve.interfaceIndex = scan.interfaceIndex;
|
|
333
|
+
resolve.timeoutSeconds = scan.resolveTimeoutSeconds;
|
|
282
334
|
scan.resolvingServices[name] = resolve;
|
|
283
335
|
[self resolve:resolve];
|
|
284
336
|
}
|
|
@@ -289,7 +341,7 @@ RCT_EXPORT_METHOD(stop:(NSString *)scanId)
|
|
|
289
341
|
resolve.addresses = [[NSMutableOrderedSet alloc] init];
|
|
290
342
|
|
|
291
343
|
DNSServiceRef ref = NULL;
|
|
292
|
-
DNSServiceErrorType error = DNSServiceResolve(&ref, 0,
|
|
344
|
+
DNSServiceErrorType error = DNSServiceResolve(&ref, 0, resolve.interfaceIndex, resolve.name.UTF8String,
|
|
293
345
|
resolve.regtype.UTF8String, resolve.domain.UTF8String,
|
|
294
346
|
RNZResolveReply, (__bridge void *)resolve);
|
|
295
347
|
if (error != kDNSServiceErr_NoError) {
|
|
@@ -304,12 +356,12 @@ RCT_EXPORT_METHOD(stop:(NSString *)scanId)
|
|
|
304
356
|
resolve.timeoutBlock = dispatch_block_create(0, ^{
|
|
305
357
|
[weakSelf resolveTimedOut:weakResolve];
|
|
306
358
|
});
|
|
307
|
-
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(resolve.
|
|
359
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(resolve.timeoutSeconds * NSEC_PER_SEC)), RNZ_QUEUE, resolve.timeoutBlock);
|
|
308
360
|
}
|
|
309
361
|
|
|
310
362
|
- (void) resolveTimedOut:(RNZResolve *)resolve
|
|
311
363
|
{
|
|
312
|
-
if (!resolve || resolve.finished || resolve.
|
|
364
|
+
if (!resolve || resolve.finished || resolve.emitted || ![self isActiveResolve:resolve]) {
|
|
313
365
|
return;
|
|
314
366
|
}
|
|
315
367
|
[self cancelResolve:resolve];
|
|
@@ -348,7 +400,7 @@ RCT_EXPORT_METHOD(stop:(NSString *)scanId)
|
|
|
348
400
|
resolve.txt = [RNNetServiceSerializer dictionaryFromTXTRecord:txt length:txtLength];
|
|
349
401
|
|
|
350
402
|
DNSServiceRef ref = NULL;
|
|
351
|
-
DNSServiceErrorType addressError = DNSServiceGetAddrInfo(&ref, 0,
|
|
403
|
+
DNSServiceErrorType addressError = DNSServiceGetAddrInfo(&ref, 0, resolve.interfaceIndex,
|
|
352
404
|
kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6,
|
|
353
405
|
host, RNZAddressReply, (__bridge void *)resolve);
|
|
354
406
|
if (addressError != kDNSServiceErr_NoError) {
|
|
@@ -376,45 +428,99 @@ RCT_EXPORT_METHOD(stop:(NSString *)scanId)
|
|
|
376
428
|
NSString *string = [RNNetServiceSerializer stringFromAddress:address];
|
|
377
429
|
if (string && (flags & kDNSServiceFlagsAdd)) {
|
|
378
430
|
[resolve.addresses addObject:string];
|
|
431
|
+
} else if (string) {
|
|
432
|
+
[resolve.addresses removeObject:string];
|
|
379
433
|
}
|
|
380
434
|
if (flags & kDNSServiceFlagsMoreComing) {
|
|
381
435
|
return;
|
|
382
436
|
}
|
|
437
|
+
[self scheduleEmit:resolve];
|
|
438
|
+
}
|
|
383
439
|
|
|
384
|
-
|
|
440
|
+
// Follows the TXT record of a resolved service
|
|
441
|
+
- (void) watchTXT:(RNZResolve *)resolve
|
|
442
|
+
{
|
|
443
|
+
char fullName[kDNSServiceMaxDomainName];
|
|
444
|
+
if (DNSServiceConstructFullName(fullName, resolve.name.UTF8String, resolve.regtype.UTF8String, resolve.domain.UTF8String) != kDNSServiceErr_NoError) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
DNSServiceRef ref = NULL;
|
|
448
|
+
if (DNSServiceQueryRecord(&ref, 0, resolve.interfaceIndex, fullName, kDNSServiceType_TXT, kDNSServiceClass_IN,
|
|
449
|
+
RNZTXTReply, (__bridge void *)resolve) != kDNSServiceErr_NoError) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
DNSServiceSetDispatchQueue(ref, RNZ_QUEUE);
|
|
453
|
+
resolve.txtRef = ref;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
- (void) handleTXT:(RNZResolve *)resolve flags:(DNSServiceFlags)flags error:(DNSServiceErrorType)error length:(uint16_t)length data:(const void *)data
|
|
457
|
+
{
|
|
458
|
+
if (resolve.finished || resolve.txtRef == NULL || error != kDNSServiceErr_NoError || !(flags & kDNSServiceFlagsAdd)) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
resolve.txt = [RNNetServiceSerializer dictionaryFromTXTRecord:data length:length];
|
|
462
|
+
[self scheduleEmit:resolve];
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// IPv4 and IPv6 answers can arrive in separate batches, wait briefly before emitting
|
|
466
|
+
- (void) scheduleEmit:(RNZResolve *)resolve
|
|
467
|
+
{
|
|
385
468
|
if (resolve.emitBlock) {
|
|
386
469
|
dispatch_block_cancel(resolve.emitBlock);
|
|
387
470
|
}
|
|
388
471
|
__weak RNZeroconf *weakSelf = self;
|
|
389
472
|
__weak RNZResolve *weakResolve = resolve;
|
|
390
473
|
resolve.emitBlock = dispatch_block_create(0, ^{
|
|
391
|
-
[weakSelf
|
|
474
|
+
[weakSelf emitResolve:weakResolve];
|
|
392
475
|
});
|
|
393
476
|
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), RNZ_QUEUE, resolve.emitBlock);
|
|
394
477
|
}
|
|
395
478
|
|
|
396
|
-
- (void)
|
|
479
|
+
- (void) emitResolve:(RNZResolve *)resolve
|
|
397
480
|
{
|
|
398
|
-
if (!resolve || resolve.finished || resolve.addresses.count == 0) {
|
|
481
|
+
if (!resolve || resolve.finished || resolve.addresses.count == 0 || ![self isActiveResolve:resolve]) {
|
|
399
482
|
return;
|
|
400
483
|
}
|
|
401
|
-
|
|
402
|
-
if (resolve.scan.resolvingServices[resolve.name] == resolve) {
|
|
403
|
-
[resolve.scan.resolvingServices removeObjectForKey:resolve.name];
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
[self sendEvent:@"RNZeroconfResolved" scan:resolve.scan body:@{
|
|
484
|
+
NSDictionary *body = @{
|
|
407
485
|
kRNServiceKeysName: resolve.name,
|
|
408
486
|
kRNServiceKeysFullName: [NSString stringWithFormat:@"%@%@.", resolve.host ?: @"", resolve.regtype],
|
|
409
487
|
kRNServiceKeysHost: resolve.host ?: @"",
|
|
410
488
|
kRNServiceKeysPort: @(resolve.port),
|
|
411
489
|
kRNServiceKeysAddresses: resolve.addresses.array,
|
|
412
490
|
kRNServiceTxtRecords: resolve.txt ?: @{},
|
|
413
|
-
}
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
if (resolve.promiseResolve) {
|
|
494
|
+
RCTPromiseResolveBlock promiseResolve = resolve.promiseResolve;
|
|
495
|
+
[self endSingleResolve:resolve];
|
|
496
|
+
promiseResolve(body);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (!resolve.emitted) {
|
|
501
|
+
resolve.emitted = YES;
|
|
502
|
+
if (resolve.timeoutBlock) {
|
|
503
|
+
dispatch_block_cancel(resolve.timeoutBlock);
|
|
504
|
+
resolve.timeoutBlock = nil;
|
|
505
|
+
}
|
|
506
|
+
[self watchTXT:resolve];
|
|
507
|
+
}
|
|
508
|
+
// Emitted again only when something changed
|
|
509
|
+
if ([body isEqualToDictionary:resolve.lastEmitted]) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
resolve.lastEmitted = body;
|
|
513
|
+
[self sendEvent:@"RNZeroconfResolved" scan:resolve.scan body:body];
|
|
414
514
|
}
|
|
415
515
|
|
|
416
516
|
- (void) failResolve:(RNZResolve *)resolve error:(NSDictionary *)error
|
|
417
517
|
{
|
|
518
|
+
if (resolve.promiseReject) {
|
|
519
|
+
RCTPromiseRejectBlock promiseReject = resolve.promiseReject;
|
|
520
|
+
[self endSingleResolve:resolve];
|
|
521
|
+
[self reject:promiseReject error:error];
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
418
524
|
[self cancelResolve:resolve];
|
|
419
525
|
if (resolve.scan.resolvingServices[resolve.name] == resolve) {
|
|
420
526
|
[resolve.scan.resolvingServices removeObjectForKey:resolve.name];
|
|
@@ -422,6 +528,54 @@ RCT_EXPORT_METHOD(stop:(NSString *)scanId)
|
|
|
422
528
|
[self sendError:error scan:resolve.scan];
|
|
423
529
|
}
|
|
424
530
|
|
|
531
|
+
- (BOOL) isActiveResolve:(RNZResolve *)resolve
|
|
532
|
+
{
|
|
533
|
+
if (resolve.promiseResolve) {
|
|
534
|
+
return [self.singleResolves containsObject:resolve];
|
|
535
|
+
}
|
|
536
|
+
return resolve.scan.resolvingServices[resolve.name] == resolve;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
- (void) endSingleResolve:(RNZResolve *)resolve
|
|
540
|
+
{
|
|
541
|
+
[self cancelResolve:resolve];
|
|
542
|
+
resolve.promiseResolve = nil;
|
|
543
|
+
resolve.promiseReject = nil;
|
|
544
|
+
[self.singleResolves removeObject:resolve];
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// resolveService(): resolves one service by name without scanning. options: timeout (seconds), networkInterface
|
|
548
|
+
RCT_EXPORT_METHOD(resolveService:(NSString *)name
|
|
549
|
+
type:(NSString *)type
|
|
550
|
+
protocol:(NSString *)protocol
|
|
551
|
+
domain:(NSString *)domain
|
|
552
|
+
options:(NSDictionary *)options
|
|
553
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
554
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
555
|
+
{
|
|
556
|
+
uint32_t interfaceIndex = kDNSServiceInterfaceIndexAny;
|
|
557
|
+
NSDictionary *interfaceError = [self interfaceIndexForName:options[@"networkInterface"] index:&interfaceIndex];
|
|
558
|
+
if (interfaceError) {
|
|
559
|
+
[self reject:reject error:interfaceError];
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
double timeout = [options[@"timeout"] respondsToSelector:@selector(doubleValue)] ? [options[@"timeout"] doubleValue] : 0;
|
|
563
|
+
|
|
564
|
+
RNZResolve *single = [[RNZResolve alloc] init];
|
|
565
|
+
single.module = self;
|
|
566
|
+
single.name = name;
|
|
567
|
+
single.regtype = [NSString stringWithFormat:@"_%@._%@", type, protocol];
|
|
568
|
+
single.domain = domain.length > 0 ? domain : @"local.";
|
|
569
|
+
single.interfaceIndex = interfaceIndex;
|
|
570
|
+
single.timeoutSeconds = timeout > 0 ? timeout : 5.0;
|
|
571
|
+
single.promiseResolve = resolve;
|
|
572
|
+
single.promiseReject = reject;
|
|
573
|
+
// The timeout is the caller's, no retry as for scans
|
|
574
|
+
single.retried = YES;
|
|
575
|
+
[self.singleResolves addObject:single];
|
|
576
|
+
[self resolve:single];
|
|
577
|
+
}
|
|
578
|
+
|
|
425
579
|
// Stops everything in flight for a resolve, safe to call from its own callbacks
|
|
426
580
|
- (void) cancelResolve:(RNZResolve *)resolve
|
|
427
581
|
{
|
|
@@ -438,6 +592,8 @@ RCT_EXPORT_METHOD(stop:(NSString *)scanId)
|
|
|
438
592
|
resolve.resolveRef = NULL;
|
|
439
593
|
RNZDeallocateLater(resolve.addressRef, resolve);
|
|
440
594
|
resolve.addressRef = NULL;
|
|
595
|
+
RNZDeallocateLater(resolve.txtRef, resolve);
|
|
596
|
+
resolve.txtRef = NULL;
|
|
441
597
|
}
|
|
442
598
|
|
|
443
599
|
#pragma mark - Publish
|
|
@@ -448,9 +604,18 @@ RCT_EXPORT_METHOD(registerService:(NSString *)type
|
|
|
448
604
|
name:(NSString *)name
|
|
449
605
|
port:(int)port
|
|
450
606
|
txt:(NSArray<NSArray<NSString *> *> *)txt
|
|
607
|
+
options:(NSDictionary *)options
|
|
451
608
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
452
609
|
reject:(RCTPromiseRejectBlock)reject)
|
|
453
610
|
{
|
|
611
|
+
uint32_t interfaceIndex = kDNSServiceInterfaceIndexAny;
|
|
612
|
+
NSDictionary *interfaceError = [self interfaceIndexForName:options[@"networkInterface"] index:&interfaceIndex];
|
|
613
|
+
if (interfaceError) {
|
|
614
|
+
[self sendError:interfaceError];
|
|
615
|
+
[self reject:reject error:interfaceError];
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
454
619
|
NSMutableArray<NSString *> *tooLong = [NSMutableArray array];
|
|
455
620
|
NSData *txtRecord = [RNNetServiceSerializer TXTRecordFromPairs:txt ?: @[] tooLong:tooLong];
|
|
456
621
|
for (NSString *key in tooLong) {
|
|
@@ -479,9 +644,19 @@ RCT_EXPORT_METHOD(registerService:(NSString *)type
|
|
|
479
644
|
publication.resolve = resolve;
|
|
480
645
|
publication.reject = reject;
|
|
481
646
|
|
|
647
|
+
// "_ipp._tcp,_printer,_color" registers the subtypes too
|
|
648
|
+
NSMutableString *registerType = [publication.regtype mutableCopy];
|
|
649
|
+
NSArray *subtypes = [options[@"subtypes"] isKindOfClass:[NSArray class]] ? options[@"subtypes"] : @[];
|
|
650
|
+
for (id subtype in subtypes) {
|
|
651
|
+
NSString *label = [RNZeroconf subtypeLabel:subtype];
|
|
652
|
+
if (label) {
|
|
653
|
+
[registerType appendFormat:@",%@", label];
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
482
657
|
DNSServiceRef ref = NULL;
|
|
483
|
-
DNSServiceErrorType error = DNSServiceRegister(&ref, 0,
|
|
484
|
-
|
|
658
|
+
DNSServiceErrorType error = DNSServiceRegister(&ref, 0, interfaceIndex, name.UTF8String,
|
|
659
|
+
registerType.UTF8String,
|
|
485
660
|
domain.length > 0 ? domain.UTF8String : NULL,
|
|
486
661
|
NULL, htons((uint16_t)port),
|
|
487
662
|
(uint16_t)txtRecord.length, txtRecord.length > 0 ? txtRecord.bytes : NULL,
|
|
@@ -565,6 +740,74 @@ RCT_EXPORT_METHOD(unregisterService:(NSString *)serviceName
|
|
|
565
740
|
resolve(serviceInfo);
|
|
566
741
|
}
|
|
567
742
|
|
|
743
|
+
// Replaces the TXT record of a published service
|
|
744
|
+
RCT_EXPORT_METHOD(updateService:(NSString *)serviceName
|
|
745
|
+
txt:(NSArray<NSArray<NSString *> *> *)txt
|
|
746
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
747
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
748
|
+
{
|
|
749
|
+
RNZPublication *publication = self.publishedServices[serviceName];
|
|
750
|
+
if (!publication) {
|
|
751
|
+
[self reject:reject error:@{
|
|
752
|
+
@"message": [NSString stringWithFormat:@"Service %@ is not published", serviceName],
|
|
753
|
+
@"code": @"NOT_PUBLISHED",
|
|
754
|
+
@"domain": @"RNZeroconf",
|
|
755
|
+
@"serviceName": serviceName,
|
|
756
|
+
}];
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
NSMutableArray<NSString *> *tooLong = [NSMutableArray array];
|
|
761
|
+
NSData *txtRecord = [RNNetServiceSerializer TXTRecordFromPairs:txt ?: @[] tooLong:tooLong];
|
|
762
|
+
for (NSString *key in tooLong) {
|
|
763
|
+
[self sendError:@{
|
|
764
|
+
@"message": [NSString stringWithFormat:@"TXT record entry %@ is longer than 255 bytes", key],
|
|
765
|
+
@"code": @"TXT_ENTRY_TOO_LONG",
|
|
766
|
+
@"domain": @"RNZeroconf",
|
|
767
|
+
@"serviceName": serviceName,
|
|
768
|
+
}];
|
|
769
|
+
}
|
|
770
|
+
// An empty TXT record is a single empty string
|
|
771
|
+
static const uint8_t emptyTXT = 0;
|
|
772
|
+
DNSServiceErrorType error = DNSServiceUpdateRecord(publication.ref, NULL, 0,
|
|
773
|
+
txtRecord.length > 0 ? (uint16_t)txtRecord.length : 1,
|
|
774
|
+
txtRecord.length > 0 ? txtRecord.bytes : &emptyTXT, 0);
|
|
775
|
+
if (error != kDNSServiceErr_NoError) {
|
|
776
|
+
[self reject:reject error:[self errorWithCode:error action:@"Updating service" serviceName:serviceName]];
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
publication.txt = [RNNetServiceSerializer dictionaryFromTXTRecord:txtRecord.bytes length:(uint16_t)txtRecord.length];
|
|
780
|
+
resolve([self publicationInfo:publication]);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// "printer" or "_printer" -> "_printer"
|
|
784
|
+
+ (NSString *) subtypeLabel:(id)subtype
|
|
785
|
+
{
|
|
786
|
+
if (![subtype isKindOfClass:[NSString class]] || [subtype length] == 0) {
|
|
787
|
+
return nil;
|
|
788
|
+
}
|
|
789
|
+
return [subtype hasPrefix:@"_"] ? subtype : [@"_" stringByAppendingString:subtype];
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// The index of a network interface by name ("en0"), kDNSServiceInterfaceIndexAny without one
|
|
793
|
+
- (NSDictionary *) interfaceIndexForName:(id)name index:(uint32_t *)index
|
|
794
|
+
{
|
|
795
|
+
*index = kDNSServiceInterfaceIndexAny;
|
|
796
|
+
if (![name isKindOfClass:[NSString class]] || [name length] == 0) {
|
|
797
|
+
return nil;
|
|
798
|
+
}
|
|
799
|
+
unsigned int found = if_nametoindex([name UTF8String]);
|
|
800
|
+
if (found == 0) {
|
|
801
|
+
return @{
|
|
802
|
+
@"message": [NSString stringWithFormat:@"Unknown network interface %@", name],
|
|
803
|
+
@"code": @"UNKNOWN_INTERFACE",
|
|
804
|
+
@"domain": @"RNZeroconf",
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
*index = found;
|
|
808
|
+
return nil;
|
|
809
|
+
}
|
|
810
|
+
|
|
568
811
|
- (BOOL) isNameInUse:(NSString *)name
|
|
569
812
|
{
|
|
570
813
|
if (self.publishedServices[name]) {
|
|
@@ -687,6 +930,10 @@ RCT_EXPORT_METHOD(checkLocalNetworkAccess:(NSString *)type
|
|
|
687
930
|
publication.ref = NULL;
|
|
688
931
|
}
|
|
689
932
|
[self.pendingPublications removeAllObjects];
|
|
933
|
+
for (RNZResolve *single in [self.singleResolves allObjects]) {
|
|
934
|
+
[self cancelResolve:single];
|
|
935
|
+
}
|
|
936
|
+
[self.singleResolves removeAllObjects];
|
|
690
937
|
}
|
|
691
938
|
|
|
692
939
|
#pragma mark - Events and errors
|
|
@@ -727,6 +974,22 @@ RCT_EXPORT_METHOD(checkLocalNetworkAccess:(NSString *)type
|
|
|
727
974
|
reject([NSString stringWithFormat:@"%@", code], error[@"message"], nsError);
|
|
728
975
|
}
|
|
729
976
|
|
|
977
|
+
- (NSDictionary *) browseErrorWithCode:(DNSServiceErrorType)code scan:(RNZScan *)scan
|
|
978
|
+
{
|
|
979
|
+
if (!scan.typesOnly) {
|
|
980
|
+
return [self errorWithCode:code action:@"Browsing services" serviceName:nil];
|
|
981
|
+
}
|
|
982
|
+
if (code == kDNSServiceErr_NoAuth) {
|
|
983
|
+
// Browsing for service types requires the multicast entitlement (Apple's Local Network Privacy FAQ)
|
|
984
|
+
return @{
|
|
985
|
+
@"message": @"Listing service types failed: not authorized, it requires the com.apple.developer.networking.multicast entitlement",
|
|
986
|
+
@"code": @(code),
|
|
987
|
+
@"domain": @"DNSSD",
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
return [self errorWithCode:code action:@"Listing service types" serviceName:nil];
|
|
991
|
+
}
|
|
992
|
+
|
|
730
993
|
- (NSDictionary *) errorWithCode:(DNSServiceErrorType)code action:(NSString *)action serviceName:(NSString *)serviceName
|
|
731
994
|
{
|
|
732
995
|
NSString *subject = serviceName ? [NSString stringWithFormat:@"%@ %@", action, serviceName] : action;
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-zeroconf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "A Zeroconf discovery utility for react-native",
|
|
5
5
|
"main": "dist",
|
|
6
6
|
"types": "index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"lint": "eslint src/*.js",
|
|
9
9
|
"format": "prettier --write src/*.js",
|
|
10
|
-
"build": "rm -rf dist && mkdir dist && babel src -o dist/index.js"
|
|
10
|
+
"build": "rm -rf dist && mkdir dist && babel src -o dist/index.js",
|
|
11
|
+
"test": "jest",
|
|
12
|
+
"typecheck": "tsc -p test/types"
|
|
11
13
|
},
|
|
12
14
|
"repository": {
|
|
13
15
|
"type": "git",
|
|
@@ -55,6 +57,11 @@
|
|
|
55
57
|
"!android/src/main/jni/mdnsresponder/mDNSShared/mDNSResponder.8",
|
|
56
58
|
"!android/build",
|
|
57
59
|
"!android/.gradle",
|
|
60
|
+
"windows",
|
|
61
|
+
"!windows/**/*.user",
|
|
62
|
+
"!windows/**/obj",
|
|
63
|
+
"!windows/**/bin",
|
|
64
|
+
"!windows/**/Generated Files",
|
|
58
65
|
"ios",
|
|
59
66
|
"react-native-zeroconf.podspec",
|
|
60
67
|
"NOTICE"
|
|
@@ -87,9 +94,14 @@
|
|
|
87
94
|
"@babel/core": "^7.23.0",
|
|
88
95
|
"@babel/preset-env": "^7.23.0",
|
|
89
96
|
"@babel/preset-flow": "^7.23.0",
|
|
97
|
+
"babel-jest": "^29.6.3",
|
|
90
98
|
"eslint": "^8.57.0",
|
|
91
99
|
"eslint-config-prettier": "^9.1.0",
|
|
92
|
-
"
|
|
100
|
+
"jest": "^29.6.3",
|
|
101
|
+
"prettier": "^3.2.0",
|
|
102
|
+
"react": "18.2.0",
|
|
103
|
+
"react-test-renderer": "18.2.0",
|
|
104
|
+
"typescript": "^5.4.0"
|
|
93
105
|
},
|
|
94
106
|
"babel": {
|
|
95
107
|
"presets": [
|
|
@@ -104,5 +116,17 @@
|
|
|
104
116
|
"@babel/preset-flow"
|
|
105
117
|
]
|
|
106
118
|
},
|
|
107
|
-
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
119
|
+
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
|
|
120
|
+
"jest": {
|
|
121
|
+
"roots": [
|
|
122
|
+
"<rootDir>/test"
|
|
123
|
+
],
|
|
124
|
+
"testEnvironment": "node",
|
|
125
|
+
"testMatch": [
|
|
126
|
+
"**/*.test.js"
|
|
127
|
+
],
|
|
128
|
+
"moduleNameMapper": {
|
|
129
|
+
"^react-native$": "<rootDir>/test/helpers/mockReactNative.js"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
108
132
|
}
|