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/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
-
import { Platform, NativeModules, DeviceEventEmitter } from 'react-native'
|
|
2
|
+
import { Platform, NativeModules, DeviceEventEmitter, PermissionsAndroid } from 'react-native'
|
|
3
3
|
import { EventEmitter } from 'events'
|
|
4
4
|
|
|
5
5
|
const RNZeroconf = NativeModules.RNZeroconf
|
|
@@ -38,6 +38,13 @@ const withDefaults = (defaults, options) => {
|
|
|
38
38
|
|
|
39
39
|
const isOptionsObject = value => value !== null && typeof value === 'object'
|
|
40
40
|
|
|
41
|
+
// Options passed to native code, without the undefined ones
|
|
42
|
+
const nativeOptions = options =>
|
|
43
|
+
Object.fromEntries(Object.entries(options).filter(([, value]) => value !== undefined))
|
|
44
|
+
|
|
45
|
+
// Android 17 (API 37) runtime permission, enforced for apps targeting API 37 or declaring it
|
|
46
|
+
const ACCESS_LOCAL_NETWORK = 'android.permission.ACCESS_LOCAL_NETWORK'
|
|
47
|
+
|
|
41
48
|
// Each instance runs its own scan, identified by this id in native events
|
|
42
49
|
let instanceCount = 0
|
|
43
50
|
|
|
@@ -90,6 +97,15 @@ const withAddressFamilies = service => {
|
|
|
90
97
|
/**
|
|
91
98
|
* TXT records as ordered [key, value] pairs, from an object or an array of pairs
|
|
92
99
|
*/
|
|
100
|
+
// Browsing this type lists the service types on the network instead of services
|
|
101
|
+
const SERVICE_TYPES = { type: 'services._dns-sd', protocol: 'udp' }
|
|
102
|
+
|
|
103
|
+
// "_http._tcp" -> { type: 'http', protocol: 'tcp' }
|
|
104
|
+
const parseServiceType = name => {
|
|
105
|
+
const match = /^_([^.]+)\._(tcp|udp)$/.exec(name)
|
|
106
|
+
return match ? { type: match[1], protocol: match[2] } : null
|
|
107
|
+
}
|
|
108
|
+
|
|
93
109
|
const toTxtPairs = txt => {
|
|
94
110
|
const entries = Array.isArray(txt) ? txt : Object.entries(txt || {})
|
|
95
111
|
return entries.map(([key, value]) => [String(key), String(value)])
|
|
@@ -106,6 +122,8 @@ export default class Zeroconf extends EventEmitter {
|
|
|
106
122
|
}
|
|
107
123
|
|
|
108
124
|
this._services = {}
|
|
125
|
+
this._serviceTypes = {}
|
|
126
|
+
this._scanningTypes = false
|
|
109
127
|
this._publishedServices = {}
|
|
110
128
|
this._publishedImplTypes = {}
|
|
111
129
|
this._scanImplType = null
|
|
@@ -148,6 +166,16 @@ export default class Zeroconf extends EventEmitter {
|
|
|
148
166
|
}
|
|
149
167
|
const { name } = service
|
|
150
168
|
|
|
169
|
+
if (this._scanningTypes) {
|
|
170
|
+
const serviceType = parseServiceType(name)
|
|
171
|
+
if (serviceType && !this._serviceTypes[name]) {
|
|
172
|
+
this._serviceTypes[name] = serviceType
|
|
173
|
+
this.emit('typeFound', serviceType)
|
|
174
|
+
this.emit('update')
|
|
175
|
+
}
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
|
|
151
179
|
this._services[name] = { name }
|
|
152
180
|
this.emit('found', name)
|
|
153
181
|
this.emit('update')
|
|
@@ -159,6 +187,16 @@ export default class Zeroconf extends EventEmitter {
|
|
|
159
187
|
}
|
|
160
188
|
const { name } = service
|
|
161
189
|
|
|
190
|
+
if (this._scanningTypes) {
|
|
191
|
+
const serviceType = this._serviceTypes[name]
|
|
192
|
+
if (serviceType) {
|
|
193
|
+
delete this._serviceTypes[name]
|
|
194
|
+
this.emit('typeRemove', serviceType)
|
|
195
|
+
this.emit('update')
|
|
196
|
+
}
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
|
|
162
200
|
delete this._services[name]
|
|
163
201
|
|
|
164
202
|
this.emit('remove', name)
|
|
@@ -237,6 +275,24 @@ export default class Zeroconf extends EventEmitter {
|
|
|
237
275
|
return this._services
|
|
238
276
|
}
|
|
239
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Get the service types found by scanServiceTypes, as [{ type, protocol }]
|
|
280
|
+
*/
|
|
281
|
+
getServiceTypes() {
|
|
282
|
+
return Object.values(this._serviceTypes)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Scan for the service types advertised on the network (_http._tcp, _ipp._tcp...), emits typeFound and typeRemove.
|
|
287
|
+
* On iOS, only types declared in NSBonjourServices can then be scanned.
|
|
288
|
+
* 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.
|
|
289
|
+
*
|
|
290
|
+
* scanServiceTypes({ domain, implType })
|
|
291
|
+
*/
|
|
292
|
+
scanServiceTypes({ domain, implType, networkInterface } = {}) {
|
|
293
|
+
this._startScan({ ...SERVICE_TYPES, domain, implType, networkInterface }, true)
|
|
294
|
+
}
|
|
295
|
+
|
|
240
296
|
/**
|
|
241
297
|
* Scan for Zeroconf services, defaults to _http._tcp. on the local. domain
|
|
242
298
|
*
|
|
@@ -244,14 +300,21 @@ export default class Zeroconf extends EventEmitter {
|
|
|
244
300
|
* scan(type, protocol, domain, implType) is deprecated
|
|
245
301
|
*/
|
|
246
302
|
scan(options, protocolArg, domainArg, implTypeArg) {
|
|
247
|
-
|
|
248
|
-
SCAN_DEFAULTS,
|
|
303
|
+
this._startScan(
|
|
249
304
|
isOptionsObject(options)
|
|
250
305
|
? options
|
|
251
306
|
: { type: options, protocol: protocolArg, domain: domainArg, implType: implTypeArg },
|
|
307
|
+
false,
|
|
252
308
|
)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
_startScan(options, scanningTypes) {
|
|
312
|
+
const { type, protocol, domain, implType, resolveTimeout, subtype, networkInterface } =
|
|
313
|
+
withDefaults(SCAN_DEFAULTS, options)
|
|
253
314
|
|
|
254
315
|
this._services = {}
|
|
316
|
+
this._serviceTypes = {}
|
|
317
|
+
this._scanningTypes = scanningTypes
|
|
255
318
|
this._hasScanned = true
|
|
256
319
|
this.emit('update')
|
|
257
320
|
if (Platform.OS === 'android') {
|
|
@@ -260,9 +323,22 @@ export default class Zeroconf extends EventEmitter {
|
|
|
260
323
|
RNZeroconf.stop(this._scanId, this._scanImplType)
|
|
261
324
|
}
|
|
262
325
|
this._scanImplType = implType
|
|
263
|
-
RNZeroconf.scan(
|
|
326
|
+
RNZeroconf.scan(
|
|
327
|
+
this._scanId,
|
|
328
|
+
type,
|
|
329
|
+
protocol,
|
|
330
|
+
domain,
|
|
331
|
+
implType,
|
|
332
|
+
nativeOptions({ subtype, networkInterface }),
|
|
333
|
+
)
|
|
264
334
|
} else {
|
|
265
|
-
RNZeroconf.scan(
|
|
335
|
+
RNZeroconf.scan(
|
|
336
|
+
this._scanId,
|
|
337
|
+
type,
|
|
338
|
+
protocol,
|
|
339
|
+
domain,
|
|
340
|
+
nativeOptions({ resolveTimeout, subtype, networkInterface }),
|
|
341
|
+
)
|
|
266
342
|
}
|
|
267
343
|
}
|
|
268
344
|
|
|
@@ -279,13 +355,22 @@ export default class Zeroconf extends EventEmitter {
|
|
|
279
355
|
}
|
|
280
356
|
|
|
281
357
|
/**
|
|
282
|
-
* Checks the
|
|
283
|
-
*
|
|
284
|
-
* 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.
|
|
285
362
|
*/
|
|
286
|
-
checkLocalNetworkAccess({ type, protocol = 'tcp', timeout = 5 } = {}) {
|
|
363
|
+
async checkLocalNetworkAccess({ type, protocol = 'tcp', timeout = 5, request = true } = {}) {
|
|
287
364
|
if (Platform.OS === 'android') {
|
|
288
|
-
|
|
365
|
+
if (!RNZeroconf.checkLocalNetworkAccess) {
|
|
366
|
+
return 'unknown'
|
|
367
|
+
}
|
|
368
|
+
const status = await asPromise(RNZeroconf.checkLocalNetworkAccess())
|
|
369
|
+
if (status !== 'denied' || !request) {
|
|
370
|
+
return status
|
|
371
|
+
}
|
|
372
|
+
const result = await PermissionsAndroid.request(ACCESS_LOCAL_NETWORK)
|
|
373
|
+
return result === PermissionsAndroid.RESULTS.GRANTED ? 'granted' : 'denied'
|
|
289
374
|
}
|
|
290
375
|
const serviceType = type ? `_${type}._${protocol}` : null
|
|
291
376
|
return asPromise(RNZeroconf.checkLocalNetworkAccess(serviceType, timeout))
|
|
@@ -299,28 +384,68 @@ export default class Zeroconf extends EventEmitter {
|
|
|
299
384
|
* publishService(type, protocol, domain, name, port, txt, implType) is deprecated
|
|
300
385
|
*/
|
|
301
386
|
publishService(options, protocolArg, domainArg, nameArg, portArg, txtArg, implTypeArg) {
|
|
302
|
-
const { type, protocol, domain, name, port, txt, implType } =
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
387
|
+
const { type, protocol, domain, name, port, txt, implType, subtypes, networkInterface } =
|
|
388
|
+
withDefaults(
|
|
389
|
+
PUBLISH_DEFAULTS,
|
|
390
|
+
isOptionsObject(options)
|
|
391
|
+
? options
|
|
392
|
+
: {
|
|
393
|
+
type: options,
|
|
394
|
+
protocol: protocolArg,
|
|
395
|
+
domain: domainArg,
|
|
396
|
+
name: nameArg,
|
|
397
|
+
port: portArg,
|
|
398
|
+
txt: txtArg,
|
|
399
|
+
implType: implTypeArg,
|
|
400
|
+
},
|
|
401
|
+
)
|
|
316
402
|
const txtRecord = toTxtPairs(txt)
|
|
403
|
+
const native = nativeOptions({ subtypes, networkInterface })
|
|
317
404
|
if (Platform.OS === 'android') {
|
|
318
405
|
this._publishedImplTypes[name] = implType
|
|
319
406
|
return asPromise(
|
|
320
|
-
RNZeroconf.registerService(type, protocol, domain, name, port, txtRecord, implType),
|
|
407
|
+
RNZeroconf.registerService(type, protocol, domain, name, port, txtRecord, implType, native),
|
|
321
408
|
)
|
|
322
409
|
}
|
|
323
|
-
return asPromise(
|
|
410
|
+
return asPromise(
|
|
411
|
+
RNZeroconf.registerService(type, protocol, domain, name, port, txtRecord, native),
|
|
412
|
+
)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Replace the TXT record of a published service, resolves with the updated service.
|
|
417
|
+
* Android NSD has no update, the service is published again under the same name.
|
|
418
|
+
*
|
|
419
|
+
* updateService(name, { txt })
|
|
420
|
+
*/
|
|
421
|
+
updateService(
|
|
422
|
+
name,
|
|
423
|
+
{ txt = {}, implType = this._publishedImplTypes[name] || ImplType.NSD } = {},
|
|
424
|
+
) {
|
|
425
|
+
const txtRecord = toTxtPairs(txt)
|
|
426
|
+
if (Platform.OS === 'android') {
|
|
427
|
+
return asPromise(RNZeroconf.updateService(name, txtRecord, implType))
|
|
428
|
+
}
|
|
429
|
+
return asPromise(RNZeroconf.updateService(name, txtRecord))
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Resolve one service by name without scanning, for a device found before.
|
|
434
|
+
* Resolves with the service, rejects with code 'TIMEOUT' when it doesn't answer in time.
|
|
435
|
+
*
|
|
436
|
+
* resolveService({ name, type, protocol, domain, implType, timeout, networkInterface })
|
|
437
|
+
*/
|
|
438
|
+
resolveService(options) {
|
|
439
|
+
const { name, type, protocol, domain, implType, timeout, networkInterface } = withDefaults(
|
|
440
|
+
{ ...SCAN_DEFAULTS, timeout: 5 },
|
|
441
|
+
options,
|
|
442
|
+
)
|
|
443
|
+
const native = nativeOptions({ timeout, networkInterface })
|
|
444
|
+
const promise =
|
|
445
|
+
Platform.OS === 'android'
|
|
446
|
+
? RNZeroconf.resolveService(name, type, protocol, domain, implType, native)
|
|
447
|
+
: RNZeroconf.resolveService(name, type, protocol, domain, native)
|
|
448
|
+
return asPromise(asPromise(promise).then(service => service && withAddressFamilies(service)))
|
|
324
449
|
}
|
|
325
450
|
|
|
326
451
|
/**
|
|
@@ -336,31 +461,23 @@ export default class Zeroconf extends EventEmitter {
|
|
|
336
461
|
}
|
|
337
462
|
}
|
|
338
463
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
* Scans again when the options change, stops and cleans up on unmount.
|
|
342
|
-
* Each hook runs its own scan, several can run at once.
|
|
343
|
-
*
|
|
344
|
-
* const { services, isScanning, error, stop, restart } = useZeroconf({ type: 'http' })
|
|
345
|
-
*/
|
|
346
|
-
export function useZeroconf(options = {}) {
|
|
347
|
-
const { type, protocol, domain, implType, resolveTimeout, enabled = true } = options
|
|
464
|
+
// Runs a scan while mounted and enabled, and again when deps change or on restart
|
|
465
|
+
function useScan(enabled, startScan, read, deps) {
|
|
348
466
|
const zeroconfRef = useRef(null)
|
|
349
|
-
const [
|
|
467
|
+
const [items, setItems] = useState([])
|
|
350
468
|
const [isScanning, setIsScanning] = useState(false)
|
|
351
469
|
const [error, setError] = useState(null)
|
|
352
470
|
const [scanCount, setScanCount] = useState(0)
|
|
471
|
+
const readRef = useRef(read)
|
|
472
|
+
readRef.current = read
|
|
353
473
|
|
|
354
474
|
useEffect(() => {
|
|
355
475
|
const zeroconf = new Zeroconf()
|
|
356
476
|
zeroconfRef.current = zeroconf
|
|
357
|
-
// Found services only have a name until they are resolved
|
|
358
|
-
const updateServices = () =>
|
|
359
|
-
setServices(Object.values(zeroconf.getServices()).filter(service => service.addresses))
|
|
360
477
|
const unsubscribes = [
|
|
361
478
|
zeroconf.subscribe('start', () => setIsScanning(true)),
|
|
362
479
|
zeroconf.subscribe('stop', () => setIsScanning(false)),
|
|
363
|
-
zeroconf.subscribe('update',
|
|
480
|
+
zeroconf.subscribe('update', () => setItems(readRef.current(zeroconf))),
|
|
364
481
|
zeroconf.subscribe('error', setError),
|
|
365
482
|
]
|
|
366
483
|
return () => {
|
|
@@ -376,11 +493,11 @@ export function useZeroconf(options = {}) {
|
|
|
376
493
|
if (!zeroconf || !enabled) {
|
|
377
494
|
return undefined
|
|
378
495
|
}
|
|
379
|
-
|
|
496
|
+
setItems([])
|
|
380
497
|
setError(null)
|
|
381
|
-
zeroconf
|
|
498
|
+
startScan(zeroconf)
|
|
382
499
|
return () => zeroconf.stop()
|
|
383
|
-
}, [enabled,
|
|
500
|
+
}, [enabled, scanCount, ...deps])
|
|
384
501
|
|
|
385
502
|
const stop = useCallback(() => {
|
|
386
503
|
if (zeroconfRef.current) {
|
|
@@ -389,5 +506,58 @@ export function useZeroconf(options = {}) {
|
|
|
389
506
|
}, [])
|
|
390
507
|
const restart = useCallback(() => setScanCount(count => count + 1), [])
|
|
391
508
|
|
|
392
|
-
return {
|
|
509
|
+
return { items, isScanning, error, stop, restart }
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Scans while mounted and returns the resolved services.
|
|
514
|
+
* Scans again when the options change, stops and cleans up on unmount.
|
|
515
|
+
* Each hook runs its own scan, several can run at once.
|
|
516
|
+
*
|
|
517
|
+
* const { services, isScanning, error, stop, restart } = useZeroconf({ type: 'http' })
|
|
518
|
+
*/
|
|
519
|
+
export function useZeroconf(options = {}) {
|
|
520
|
+
const {
|
|
521
|
+
type,
|
|
522
|
+
protocol,
|
|
523
|
+
domain,
|
|
524
|
+
implType,
|
|
525
|
+
resolveTimeout,
|
|
526
|
+
subtype,
|
|
527
|
+
networkInterface,
|
|
528
|
+
enabled = true,
|
|
529
|
+
} = options
|
|
530
|
+
const { items, ...scan } = useScan(
|
|
531
|
+
enabled,
|
|
532
|
+
zeroconf =>
|
|
533
|
+
zeroconf.scan({
|
|
534
|
+
type,
|
|
535
|
+
protocol,
|
|
536
|
+
domain,
|
|
537
|
+
implType,
|
|
538
|
+
resolveTimeout,
|
|
539
|
+
subtype,
|
|
540
|
+
networkInterface,
|
|
541
|
+
}),
|
|
542
|
+
// Found services only have a name until they are resolved
|
|
543
|
+
zeroconf => Object.values(zeroconf.getServices()).filter(service => service.addresses),
|
|
544
|
+
[type, protocol, domain, implType, resolveTimeout, subtype, networkInterface],
|
|
545
|
+
)
|
|
546
|
+
return { services: items, ...scan }
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* Lists the service types advertised on the network while mounted.
|
|
551
|
+
*
|
|
552
|
+
* const { serviceTypes, isScanning, error, stop, restart } = useServiceTypes()
|
|
553
|
+
*/
|
|
554
|
+
export function useServiceTypes(options = {}) {
|
|
555
|
+
const { domain, implType, networkInterface, enabled = true } = options
|
|
556
|
+
const { items, ...scan } = useScan(
|
|
557
|
+
enabled,
|
|
558
|
+
zeroconf => zeroconf.scanServiceTypes({ domain, implType, networkInterface }),
|
|
559
|
+
zeroconf => zeroconf.getServiceTypes(),
|
|
560
|
+
[domain, implType, networkInterface],
|
|
561
|
+
)
|
|
562
|
+
return { serviceTypes: items, ...scan }
|
|
393
563
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
|
3
|
+
|
|
4
|
+
<PropertyGroup Label="Microsoft.ReactNative Experimental Features">
|
|
5
|
+
<!--
|
|
6
|
+
Required for building a New Architecture project.
|
|
7
|
+
|
|
8
|
+
Library projects can change this value to test against Old and New
|
|
9
|
+
Architectures when building the library project's local sln file.
|
|
10
|
+
|
|
11
|
+
Otherwise this value will be decided by the consuming RNW app.
|
|
12
|
+
|
|
13
|
+
See https://microsoft.github.io/react-native-windows/docs/new-architecture
|
|
14
|
+
-->
|
|
15
|
+
<RnwNewArch>true</RnwNewArch>
|
|
16
|
+
|
|
17
|
+
<!--
|
|
18
|
+
Changes compilation to assume use of Microsoft.ReactNative NuGet packages
|
|
19
|
+
instead of building the framework from source. Defaults to true.
|
|
20
|
+
|
|
21
|
+
This is set during library project creation and is used when building
|
|
22
|
+
the library project's local sln file.
|
|
23
|
+
|
|
24
|
+
Otherwise this value will be decided by the consuming RNW app.
|
|
25
|
+
|
|
26
|
+
See https://microsoft.github.io/react-native-windows/docs/nuget
|
|
27
|
+
-->
|
|
28
|
+
<UseExperimentalNuget>true</UseExperimentalNuget>
|
|
29
|
+
|
|
30
|
+
<ReactExperimentalFeaturesSet>true</ReactExperimentalFeaturesSet>
|
|
31
|
+
</PropertyGroup>
|
|
32
|
+
|
|
33
|
+
</Project>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<configuration>
|
|
3
|
+
<packageSources>
|
|
4
|
+
<clear />
|
|
5
|
+
<add key="react-native" value="https://pkgs.dev.azure.com/ms/react-native/_packaging/react-native-public/nuget/v3/index.json" />
|
|
6
|
+
<add key="Nuget.org" value="https://api.nuget.org/v3/index.json" />
|
|
7
|
+
</packageSources>
|
|
8
|
+
<disabledPackageSources>
|
|
9
|
+
<clear />
|
|
10
|
+
</disabledPackageSources>
|
|
11
|
+
</configuration>
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#include "pch.h"
|
|
2
|
+
|
|
3
|
+
#include "RNZeroconf.h"
|
|
4
|
+
|
|
5
|
+
using namespace rnzeroconf;
|
|
6
|
+
|
|
7
|
+
namespace winrt::RNZeroconf
|
|
8
|
+
{
|
|
9
|
+
|
|
10
|
+
namespace {
|
|
11
|
+
|
|
12
|
+
std::wstring Wide(std::string const &value) {
|
|
13
|
+
return Widen(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
std::wstring StringOption(React::JSValue const &options, char const *key) {
|
|
17
|
+
if (options.Type() != React::JSValueType::Object) {
|
|
18
|
+
return L"";
|
|
19
|
+
}
|
|
20
|
+
auto const &value = options.AsObject();
|
|
21
|
+
auto it = value.find(key);
|
|
22
|
+
return it != value.end() && it->second.Type() == React::JSValueType::String ? Wide(it->second.AsString()) : L"";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
double NumberOption(React::JSValue const &options, char const *key) {
|
|
26
|
+
if (options.Type() != React::JSValueType::Object) {
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
auto const &value = options.AsObject();
|
|
30
|
+
auto it = value.find(key);
|
|
31
|
+
return it != value.end() && (it->second.Type() == React::JSValueType::Double || it->second.Type() == React::JSValueType::Int64)
|
|
32
|
+
? it->second.AsDouble()
|
|
33
|
+
: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// [[key, value], ...] from JavaScript
|
|
37
|
+
TxtPairs ReadTxt(React::JSValue const &txt) {
|
|
38
|
+
TxtPairs pairs;
|
|
39
|
+
if (txt.Type() != React::JSValueType::Array) {
|
|
40
|
+
return pairs;
|
|
41
|
+
}
|
|
42
|
+
for (auto const &pair : txt.AsArray()) {
|
|
43
|
+
if (pair.Type() == React::JSValueType::Array && pair.AsArray().size() == 2) {
|
|
44
|
+
pairs.emplace_back(Wide(pair.AsArray()[0].AsString()), Wide(pair.AsArray()[1].AsString()));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return pairs;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
React::JSValueObject ServiceObject(Service const &service) {
|
|
51
|
+
React::JSValueArray addresses;
|
|
52
|
+
for (auto const &address : service.addresses) {
|
|
53
|
+
addresses.push_back(Narrow(address));
|
|
54
|
+
}
|
|
55
|
+
React::JSValueObject txt;
|
|
56
|
+
for (auto const &pair : service.txt) {
|
|
57
|
+
txt[Narrow(pair.first)] = Narrow(pair.second);
|
|
58
|
+
}
|
|
59
|
+
React::JSValueObject object;
|
|
60
|
+
object["name"] = Narrow(service.name);
|
|
61
|
+
object["fullName"] = Narrow(service.fullName);
|
|
62
|
+
object["host"] = Narrow(service.host);
|
|
63
|
+
object["port"] = static_cast<int64_t>(service.port);
|
|
64
|
+
object["addresses"] = std::move(addresses);
|
|
65
|
+
object["txt"] = std::move(txt);
|
|
66
|
+
return object;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
React::JSValueObject ErrorObject(Error const &error) {
|
|
70
|
+
React::JSValueObject object;
|
|
71
|
+
object["message"] = Narrow(error.message);
|
|
72
|
+
if (error.stringCode.empty()) {
|
|
73
|
+
object["code"] = error.code;
|
|
74
|
+
} else {
|
|
75
|
+
object["code"] = Narrow(error.stringCode);
|
|
76
|
+
}
|
|
77
|
+
object["domain"] = Narrow(error.domain);
|
|
78
|
+
if (!error.serviceName.empty()) {
|
|
79
|
+
object["serviceName"] = Narrow(error.serviceName);
|
|
80
|
+
}
|
|
81
|
+
return object;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Rejects with the same { message, code, domain, serviceName } in userInfo as iOS and Android
|
|
85
|
+
void Reject(React::ReactPromise<React::JSValue> const &promise, Error const &error) {
|
|
86
|
+
React::ReactError reactError;
|
|
87
|
+
reactError.Code = error.stringCode.empty() ? std::to_string(error.code) : Narrow(error.stringCode);
|
|
88
|
+
reactError.Message = Narrow(error.message);
|
|
89
|
+
reactError.UserInfo = ErrorObject(error);
|
|
90
|
+
promise.Reject(std::move(reactError));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
} // namespace
|
|
94
|
+
|
|
95
|
+
void RNZeroconf::Initialize(React::ReactContext const &reactContext) noexcept {
|
|
96
|
+
m_context = reactContext;
|
|
97
|
+
|
|
98
|
+
Events events;
|
|
99
|
+
events.start = [this](std::string const &scanId) {
|
|
100
|
+
React::JSValueObject body;
|
|
101
|
+
body["scanId"] = scanId;
|
|
102
|
+
Emit(L"RNZeroconfStart", std::move(body));
|
|
103
|
+
};
|
|
104
|
+
events.stop = [this](std::string const &scanId) {
|
|
105
|
+
React::JSValueObject body;
|
|
106
|
+
body["scanId"] = scanId;
|
|
107
|
+
Emit(L"RNZeroconfStop", std::move(body));
|
|
108
|
+
};
|
|
109
|
+
events.found = [this](std::string const &scanId, std::wstring const &name) {
|
|
110
|
+
React::JSValueObject body;
|
|
111
|
+
body["name"] = Narrow(name);
|
|
112
|
+
body["scanId"] = scanId;
|
|
113
|
+
Emit(L"RNZeroconfFound", std::move(body));
|
|
114
|
+
};
|
|
115
|
+
events.remove = [this](std::string const &scanId, std::wstring const &name) {
|
|
116
|
+
React::JSValueObject body;
|
|
117
|
+
body["name"] = Narrow(name);
|
|
118
|
+
body["scanId"] = scanId;
|
|
119
|
+
Emit(L"RNZeroconfRemove", std::move(body));
|
|
120
|
+
};
|
|
121
|
+
events.resolved = [this](std::string const &scanId, Service const &service) {
|
|
122
|
+
React::JSValueObject body = ServiceObject(service);
|
|
123
|
+
body["scanId"] = scanId;
|
|
124
|
+
Emit(L"RNZeroconfResolved", std::move(body));
|
|
125
|
+
};
|
|
126
|
+
events.published = [this](Service const &service) { Emit(L"RNZeroconfServiceRegistered", ServiceObject(service)); };
|
|
127
|
+
events.unpublished = [this](Service const &service) { Emit(L"RNZeroconfServiceUnregistered", ServiceObject(service)); };
|
|
128
|
+
events.error = [this](std::string const &scanId, Error const &error) {
|
|
129
|
+
React::JSValueObject body = ErrorObject(error);
|
|
130
|
+
if (!scanId.empty()) {
|
|
131
|
+
body["scanId"] = scanId;
|
|
132
|
+
}
|
|
133
|
+
Emit(L"RNZeroconfError", std::move(body));
|
|
134
|
+
};
|
|
135
|
+
m_zeroconf = std::make_unique<Zeroconf>(std::move(events));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
void RNZeroconf::Emit(std::wstring_view eventName, React::JSValueObject body) noexcept {
|
|
139
|
+
m_context.EmitJSEvent(L"RCTDeviceEventEmitter", eventName, std::move(body));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
void RNZeroconf::scan(std::string scanId, std::string type, std::string protocol, std::string domain, React::JSValue options) noexcept {
|
|
143
|
+
m_zeroconf->Scan(scanId, Wide(type), Wide(protocol), Wide(domain), StringOption(options, "subtype"), StringOption(options, "networkInterface"));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
void RNZeroconf::stop(React::JSValue scanId) noexcept {
|
|
147
|
+
if (scanId.Type() == React::JSValueType::String && !scanId.AsString().empty()) {
|
|
148
|
+
m_zeroconf->Stop(scanId.AsString());
|
|
149
|
+
} else {
|
|
150
|
+
m_zeroconf->StopAll();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
void RNZeroconf::registerService(
|
|
155
|
+
std::string type,
|
|
156
|
+
std::string protocol,
|
|
157
|
+
std::string domain,
|
|
158
|
+
std::string name,
|
|
159
|
+
int port,
|
|
160
|
+
React::JSValue txt,
|
|
161
|
+
React::JSValue options,
|
|
162
|
+
React::ReactPromise<React::JSValue> promise) noexcept {
|
|
163
|
+
// windns.h can't register subtypes
|
|
164
|
+
if (options.Type() == React::JSValueType::Object) {
|
|
165
|
+
auto const &object = options.AsObject();
|
|
166
|
+
auto subtypes = object.find("subtypes");
|
|
167
|
+
if (subtypes != object.end() && subtypes->second.Type() == React::JSValueType::Array && !subtypes->second.AsArray().empty()) {
|
|
168
|
+
Error error;
|
|
169
|
+
error.domain = L"RNZeroconf";
|
|
170
|
+
error.stringCode = L"UNSUPPORTED";
|
|
171
|
+
error.message = L"Publishing subtypes is not supported on Windows";
|
|
172
|
+
error.serviceName = Wide(name);
|
|
173
|
+
Reject(promise, error);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
m_zeroconf->Publish(
|
|
178
|
+
Wide(type),
|
|
179
|
+
Wide(protocol),
|
|
180
|
+
Wide(domain),
|
|
181
|
+
Wide(name),
|
|
182
|
+
static_cast<uint16_t>(port),
|
|
183
|
+
ReadTxt(txt),
|
|
184
|
+
StringOption(options, "networkInterface"),
|
|
185
|
+
[promise](Service const &service) mutable { promise.Resolve(React::JSValue(ServiceObject(service))); },
|
|
186
|
+
[promise](Error const &error) mutable { Reject(promise, error); });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
void RNZeroconf::unregisterService(std::string name, React::ReactPromise<React::JSValue> promise) noexcept {
|
|
190
|
+
m_zeroconf->Unpublish(
|
|
191
|
+
Wide(name),
|
|
192
|
+
[promise](Service const &service) mutable { promise.Resolve(React::JSValue(ServiceObject(service))); },
|
|
193
|
+
[promise](Error const &error) mutable { Reject(promise, error); });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
void RNZeroconf::updateService(std::string name, React::JSValue txt, React::ReactPromise<React::JSValue> promise) noexcept {
|
|
197
|
+
m_zeroconf->Update(
|
|
198
|
+
Wide(name),
|
|
199
|
+
ReadTxt(txt),
|
|
200
|
+
[promise](Service const &service) mutable { promise.Resolve(React::JSValue(ServiceObject(service))); },
|
|
201
|
+
[promise](Error const &error) mutable { Reject(promise, error); });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
void RNZeroconf::resolveService(
|
|
205
|
+
std::string name,
|
|
206
|
+
std::string type,
|
|
207
|
+
std::string protocol,
|
|
208
|
+
std::string domain,
|
|
209
|
+
React::JSValue options,
|
|
210
|
+
React::ReactPromise<React::JSValue> promise) noexcept {
|
|
211
|
+
m_zeroconf->ResolveService(
|
|
212
|
+
Wide(name),
|
|
213
|
+
Wide(type),
|
|
214
|
+
Wide(protocol),
|
|
215
|
+
Wide(domain),
|
|
216
|
+
StringOption(options, "networkInterface"),
|
|
217
|
+
NumberOption(options, "timeout"),
|
|
218
|
+
[promise](Service const &service) mutable { promise.Resolve(React::JSValue(ServiceObject(service))); },
|
|
219
|
+
[promise](Error const &error) mutable { Reject(promise, error); });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
void RNZeroconf::checkLocalNetworkAccess(React::JSValue, React::JSValue, React::ReactPromise<React::JSValue> promise) noexcept {
|
|
223
|
+
// Nothing to grant on Windows, as on Android before Android 17
|
|
224
|
+
promise.Resolve(React::JSValue("granted"));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
} // namespace winrt::RNZeroconf
|