ilabs-flir 2.1.1 → 2.1.3

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.
@@ -310,23 +310,57 @@ import ThermalSDK
310
310
  discovery?.delegate = self
311
311
  }
312
312
 
313
- // Start discovery on all available interfaces
314
- let interfaces: FLIRCommunicationInterface = [
313
+ // Build interfaces based on available permissions
314
+ // Always include Lightning, USB, Wireless BLE, and Emulator
315
+ var interfaces: FLIRCommunicationInterface = [
315
316
  .lightning,
316
- .network,
317
317
  .flirOneWireless,
318
318
  .emulator
319
319
  ]
320
+
321
+ // Only add network discovery if NSLocalNetworkUsageDescription is present
322
+ // This prevents crashes/errors when user doesn't have iOS developer registration
323
+ // or hasn't declared network permission
324
+ if shouldEnableNetworkDiscovery() {
325
+ interfaces.insert(.network)
326
+ NSLog("[FlirManager] Network discovery enabled (NSLocalNetworkUsageDescription present)")
327
+ } else {
328
+ NSLog("[FlirManager] Network discovery disabled (no NSLocalNetworkUsageDescription)")
329
+ }
330
+
320
331
  discovery?.start(interfaces)
321
332
 
322
333
  emitStateChange("discovering")
323
- NSLog("[FlirManager] Discovery started on interfaces: Lightning, Network, FlirOneWireless, Emulator")
334
+ NSLog("[FlirManager] Discovery started on interfaces: Lightning, \(interfaces.contains(.network) ? "Network, " : "")FlirOneWireless, Emulator")
324
335
  #else
325
336
  NSLog("[FlirManager] FLIR SDK not available - discovery disabled")
326
337
  delegate?.onError("FLIR SDK not available")
327
338
  #endif
328
339
  }
329
340
 
341
+ /// Check if network discovery should be enabled based on Info.plist permission
342
+ private func shouldEnableNetworkDiscovery() -> Bool {
343
+ // Check for explicit override first
344
+ let key = "ilabsFlir.networkDiscoveryEnabled"
345
+ if UserDefaults.standard.object(forKey: key) != nil {
346
+ return UserDefaults.standard.bool(forKey: key)
347
+ }
348
+
349
+ // Safe default: require Local Network usage description to be present
350
+ if let desc = Bundle.main.object(forInfoDictionaryKey: "NSLocalNetworkUsageDescription") as? String,
351
+ !desc.isEmpty {
352
+ return true
353
+ }
354
+
355
+ return false
356
+ }
357
+
358
+ /// Allow explicit override of network discovery (called from React Native)
359
+ @objc public func setNetworkDiscoveryEnabled(_ enabled: Bool) {
360
+ UserDefaults.standard.set(enabled, forKey: "ilabsFlir.networkDiscoveryEnabled")
361
+ NSLog("[FlirManager] Network discovery override set to: \(enabled)")
362
+ }
363
+
330
364
  @objc public func stopDiscovery() {
331
365
  NSLog("[FlirManager] Stopping discovery...")
332
366
 
@@ -366,64 +400,72 @@ import ThermalSDK
366
400
  }
367
401
 
368
402
  private func performConnection(identity: FLIRIdentity) {
369
- do {
370
- if camera == nil {
371
- camera = FLIRCamera()
372
- camera?.delegate = self
403
+ // Use the proven connection pattern from FLIR SDK samples:
404
+ // FLIROneCameraSwift uses: pair(identity, code:) then connect()
405
+
406
+ if camera == nil {
407
+ camera = FLIRCamera()
408
+ camera?.delegate = self
409
+ }
410
+
411
+ guard let cam = camera else {
412
+ NSLog("[FlirManager] Failed to create FLIRCamera")
413
+ DispatchQueue.main.async { [weak self] in
414
+ self?.delegate?.onError("Failed to create camera instance")
373
415
  }
374
-
375
- // Handle authentication for generic cameras
376
- if identity.cameraType() == .generic {
377
- let certName = getCertificateName()
378
- var status = FLIRAuthenticationStatus.pending
379
- while status == .pending {
380
- status = camera!.authenticate(identity, trustedConnectionName: certName)
381
- if status == .pending {
382
- NSLog("[FlirManager] Waiting for camera authentication approval...")
383
- Thread.sleep(forTimeInterval: 1.0)
384
- }
416
+ return
417
+ }
418
+
419
+ // Handle authentication for generic cameras (network cameras)
420
+ if identity.cameraType() == .generic {
421
+ let certName = getCertificateName()
422
+ var status = FLIRAuthenticationStatus.pending
423
+ while status == .pending {
424
+ status = cam.authenticate(identity, trustedConnectionName: certName)
425
+ if status == .pending {
426
+ NSLog("[FlirManager] Waiting for camera authentication approval...")
427
+ Thread.sleep(forTimeInterval: 1.0)
385
428
  }
386
429
  }
430
+ NSLog("[FlirManager] Authentication status: \(status.rawValue)")
431
+ }
432
+
433
+ do {
434
+ // Step 1: Pair with identity (required for FLIR One devices)
435
+ // The code parameter is for BLE pairing, 0 for direct connection
436
+ try cam.pair(identity, code: 0)
437
+ NSLog("[FlirManager] Paired with: \(identity.deviceId())")
387
438
 
388
- // Connect (support multiple SDK signatures via ObjC runtime)
389
- var connectedOK = false
390
- if let cam = camera {
391
- if cam.responds(to: Selector(("connect:error:"))) {
392
- // Call connect:identity error:nil (ignore NSError pointer for compatibility)
393
- _ = cam.perform(Selector(("connect:error:")), with: identity, with: nil)
394
- connectedOK = true
395
- } else if cam.responds(to: Selector(("connect:"))) {
396
- _ = cam.perform(Selector(("connect:")), with: identity)
397
- connectedOK = true
398
- } else {
399
- NSLog("[FlirManager] No compatible connect API on FLIRCamera")
400
- }
401
- }
402
-
403
- if connectedOK {
404
- connectedIdentity = identity
405
- connectedDeviceId = identity.deviceId()
406
- connectedDeviceName = identity.deviceId()
407
- _isConnected = true
408
- NSLog("[FlirManager] Connected to: \(identity.deviceId())")
409
- } else {
410
- NSLog("[FlirManager] Connection failed: no compatible connect API")
411
- DispatchQueue.main.async { [weak self] in
412
- self?.delegate?.onError("Connection failed: unsupported SDK API")
413
- }
414
- return
439
+ // Step 2: Connect (no identity parameter - uses paired identity)
440
+ try cam.connect()
441
+ NSLog("[FlirManager] Connected to: \(identity.deviceId())")
442
+
443
+ // Update state
444
+ connectedIdentity = identity
445
+ connectedDeviceId = identity.deviceId()
446
+ connectedDeviceName = identity.deviceId()
447
+ _isConnected = true
448
+
449
+ // Get camera info if available
450
+ if let remoteControl = cam.getRemoteControl(),
451
+ let cameraInfo = try? remoteControl.getCameraInformation() {
452
+ NSLog("[FlirManager] Camera info: \(cameraInfo)")
415
453
  }
416
454
 
417
455
  // Get streams
418
- if let streams = camera?.getStreams(), !streams.isEmpty {
456
+ if let streams = cam.getStreams(), !streams.isEmpty {
419
457
  NSLog("[FlirManager] Found \(streams.count) streams")
420
458
 
421
- // Auto-start first thermal stream
422
- if let firstStream = streams.first {
423
- startStreamInternal(firstStream)
459
+ // Find and start the first thermal stream (preferred) or any stream
460
+ let thermalStream = streams.first { $0.isThermal } ?? streams.first
461
+ if let streamToStart = thermalStream {
462
+ startStreamInternal(streamToStart)
424
463
  }
464
+ } else {
465
+ NSLog("[FlirManager] No streams available on camera")
425
466
  }
426
467
 
468
+ // Notify delegate on main thread
427
469
  DispatchQueue.main.async { [weak self] in
428
470
  guard let self = self else { return }
429
471
  let deviceInfo = FlirDeviceInfo(
@@ -437,7 +479,9 @@ import ThermalSDK
437
479
  }
438
480
 
439
481
  } catch {
440
- NSLog("[FlirManager] Connection failed: \(error)")
482
+ NSLog("[FlirManager] Connection failed: \(error.localizedDescription)")
483
+ _isConnected = false
484
+ camera = nil
441
485
  DispatchQueue.main.async { [weak self] in
442
486
  self?.delegate?.onError("Connection failed: \(error.localizedDescription)")
443
487
  }
@@ -156,6 +156,39 @@ RCT_EXPORT_METHOD(removeListeners:(NSInteger)count) {
156
156
 
157
157
  #pragma mark - Discovery Methods
158
158
 
159
+ // Network discovery on iOS 14+ requires Local Network privacy keys.
160
+ // In USB/Bluetooth-only builds (or when the user denied permission), attempting
161
+ // Bonjour discovery can fail noisily or crash depending on SDK internals.
162
+ // We default to enabling network discovery only when the host app declares
163
+ // NSLocalNetworkUsageDescription, and allow an explicit override via
164
+ // setNetworkDiscoveryEnabled.
165
+ - (BOOL)shouldEnableNetworkDiscovery {
166
+ // Explicit override if app sets it.
167
+ NSString *key = @"ilabsFlir.networkDiscoveryEnabled";
168
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
169
+ if ([defaults objectForKey:key] != nil) {
170
+ return [defaults boolForKey:key];
171
+ }
172
+
173
+ // Safe default: require Local Network usage description to be present.
174
+ id desc = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSLocalNetworkUsageDescription"];
175
+ if ([desc isKindOfClass:[NSString class]] && ((NSString *)desc).length > 0) {
176
+ return YES;
177
+ }
178
+ return NO;
179
+ }
180
+
181
+ RCT_EXPORT_METHOD(setNetworkDiscoveryEnabled:(BOOL)enabled
182
+ resolver:(RCTPromiseResolveBlock)resolve
183
+ rejecter:(RCTPromiseRejectBlock)reject) {
184
+ #if FLIR_SDK_AVAILABLE
185
+ [[NSUserDefaults standardUserDefaults] setBool:enabled forKey:@"ilabsFlir.networkDiscoveryEnabled"];
186
+ resolve(@(YES));
187
+ #else
188
+ resolve(@(YES));
189
+ #endif
190
+ }
191
+
159
192
  RCT_EXPORT_METHOD(startDiscovery:(RCTPromiseResolveBlock)resolve
160
193
  rejecter:(RCTPromiseRejectBlock)reject) {
161
194
  #if FLIR_SDK_AVAILABLE
@@ -197,11 +230,18 @@ RCT_EXPORT_METHOD(startDiscovery:(RCTPromiseResolveBlock)resolve
197
230
  self.discovery.delegate = self;
198
231
  }
199
232
 
200
- // Start discovery on all available interfaces
233
+ // Start discovery on allowed interfaces.
234
+ // Always include wired/BLE/emulator. Only include network when the app has
235
+ // Local Network usage description (or the app explicitly enabled it).
201
236
  FLIRCommunicationInterface interfaces = FLIRCommunicationInterfaceLightning |
202
- FLIRCommunicationInterfaceNetwork |
203
237
  FLIRCommunicationInterfaceFlirOneWireless |
204
- FLIRCommunicationInterfaceEmulator;
238
+ FLIRCommunicationInterfaceEmulator |
239
+ FLIRCommunicationInterfaceUSB;
240
+ if ([self shouldEnableNetworkDiscovery]) {
241
+ interfaces |= FLIRCommunicationInterfaceNetwork;
242
+ } else {
243
+ RCTLogInfo(@"[FlirModule] Network discovery disabled (missing NSLocalNetworkUsageDescription or overridden)");
244
+ }
205
245
  [self.discovery start:interfaces];
206
246
 
207
247
  [self emitStateChange:@"discovering"];
@@ -323,7 +363,7 @@ RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
323
363
 
324
364
  NSError *error = nil;
325
365
 
326
- // Handle authentication for generic cameras
366
+ // Handle authentication for generic cameras (network cameras)
327
367
  if ([identity cameraType] == FLIRCameraType_generic) {
328
368
  NSString *certName = [self getCertificateName];
329
369
  FLIRAuthenticationStatus status = pending;
@@ -334,9 +374,34 @@ RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
334
374
  [NSThread sleepForTimeInterval:1.0];
335
375
  }
336
376
  }
377
+ RCTLogInfo(@"[FlirModule] Authentication status: %d", (int)status);
337
378
  }
338
379
 
339
- BOOL connected = [self.camera connect:identity error:&error];
380
+ // Use the proven connection pattern from FLIR SDK samples:
381
+ // Step 1: Pair with identity (required for FLIR One devices)
382
+ @try {
383
+ [self.camera pair:identity code:0];
384
+ RCTLogInfo(@"[FlirModule] Paired with: %@", [identity deviceId]);
385
+ } @catch (NSException *exception) {
386
+ RCTLogError(@"[FlirModule] Pair failed: %@", exception.reason);
387
+ NSError *pairError = [NSError errorWithDomain:@"FlirModule" code:1001 userInfo:@{NSLocalizedDescriptionKey: exception.reason ?: @"Pair failed"}];
388
+ if (completion) completion(NO, pairError);
389
+ return;
390
+ }
391
+
392
+ // Step 2: Connect (no identity parameter - uses paired identity)
393
+ BOOL connected = NO;
394
+ @try {
395
+ [self.camera connect:&error];
396
+ connected = (error == nil);
397
+ if (connected) {
398
+ RCTLogInfo(@"[FlirModule] Connected to: %@", [identity deviceId]);
399
+ }
400
+ } @catch (NSException *exception) {
401
+ RCTLogError(@"[FlirModule] Connect exception: %@", exception.reason);
402
+ error = [NSError errorWithDomain:@"FlirModule" code:1002 userInfo:@{NSLocalizedDescriptionKey: exception.reason ?: @"Connect failed"}];
403
+ connected = NO;
404
+ }
340
405
 
341
406
  if (connected) {
342
407
  self.connectedIdentity = identity;
@@ -352,14 +417,25 @@ RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
352
417
  self.connectedDeviceName = displayName;
353
418
  self.isConnected = YES;
354
419
 
355
- RCTLogInfo(@"[FlirModule] Connected to: %@", [identity deviceId]);
420
+ RCTLogInfo(@"[FlirModule] Successfully connected to: %@", displayName);
356
421
 
357
- // Get available streams
422
+ // Get available streams and prefer thermal stream
358
423
  NSArray<FLIRStream *> *streams = [self.camera getStreams];
359
424
  if (streams.count > 0) {
360
425
  RCTLogInfo(@"[FlirModule] Found %lu streams", (unsigned long)streams.count);
361
- // Auto-start first stream
362
- [self startStreamInternal:streams[0]];
426
+
427
+ // Find thermal stream (preferred) or use first stream
428
+ FLIRStream *streamToStart = nil;
429
+ for (FLIRStream *stream in streams) {
430
+ if (stream.isThermal) {
431
+ streamToStart = stream;
432
+ break;
433
+ }
434
+ }
435
+ if (!streamToStart) {
436
+ streamToStart = streams[0];
437
+ }
438
+ [self startStreamInternal:streamToStart];
363
439
  }
364
440
 
365
441
  dispatch_async(dispatch_get_main_queue(), ^{
@@ -369,10 +445,12 @@ RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
369
445
  if (completion) completion(YES, nil);
370
446
  } else {
371
447
  RCTLogError(@"[FlirModule] Connection failed: %@", error.localizedDescription);
448
+ self.camera = nil;
372
449
  if (completion) completion(NO, error);
373
450
  }
374
451
  }
375
452
 
453
+
376
454
  - (NSString *)getCertificateName {
377
455
  NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier] ?: @"com.flir.app";
378
456
  NSString *key = [NSString stringWithFormat:@"%@-cert-name", bundleID];
@@ -821,8 +899,15 @@ RCT_EXPORT_METHOD(isPreferSdkRotation:(RCTPromiseResolveBlock)resolve
821
899
  }
822
900
 
823
901
  - (void)discoveryError:(NSString *)error netServiceError:(int)nsnetserviceserror on:(FLIRCommunicationInterface)iface {
902
+ // Network discovery failures are expected when Local Network permission is missing/denied.
903
+ // Do not surface those as fatal errors; keep USB/BLE discovery running.
904
+ if ((iface & FLIRCommunicationInterfaceNetwork) == FLIRCommunicationInterfaceNetwork) {
905
+ RCTLogInfo(@"[FlirModule] Network discovery error (suppressed): %@ (%d)", error, nsnetserviceserror);
906
+ return;
907
+ }
908
+
824
909
  RCTLogError(@"[FlirModule] Discovery error: %@ (%d)", error, nsnetserviceserror);
825
-
910
+
826
911
  dispatch_async(dispatch_get_main_queue(), ^{
827
912
  [[FlirEventEmitter shared] sendDeviceEvent:@"FlirError" body:@{
828
913
  @"error": error ?: @"Unknown discovery error",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.1.1",
3
+ "version": "2.1.3",
4
4
  "description": "FLIR Thermal SDK for React Native - iOS & Android (bundled at compile time via postinstall)",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",