ilabs-flir 2.2.4 → 2.2.6

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.
@@ -74,9 +74,8 @@ import ThermalSDK
74
74
  // Client lifecycle for discovery/connection ownership
75
75
  private var activeClients: Set<String> = []
76
76
  private var shutdownWorkItem: DispatchWorkItem? = nil
77
- // Discovery & stream watchdogs to detect silent failures/timeouts
77
+ // Discovery timeout to prevent infinite scanning
78
78
  private var discoveryTimeoutWorkItem: DispatchWorkItem? = nil
79
- private var streamWatchdogWorkItem: DispatchWorkItem? = nil
80
79
 
81
80
  // Battery polling timer (like Android)
82
81
  private var batteryPollingTimer: Timer?
@@ -89,6 +88,7 @@ import ThermalSDK
89
88
  private var stream: FLIRStream?
90
89
  private var streamer: FLIRThermalStreamer?
91
90
  private var connectedIdentity: FLIRIdentity?
91
+ private var identityMap: [String: FLIRIdentity] = [:]
92
92
  #endif
93
93
 
94
94
  private override init() {
@@ -402,27 +402,26 @@ import ThermalSDK
402
402
  discovery?.start(interfaces)
403
403
 
404
404
  emitStateChange("discovering")
405
-
406
- // Cancel any previous discovery timeout and schedule a new one so
407
- // the UI doesn't remain stuck when discovery yields no devices.
405
+
406
+ // Set timeout to prevent infinite scanning (matches Android's 8-second timeout)
408
407
  discoveryTimeoutWorkItem?.cancel()
409
- let discWork = DispatchWorkItem { [weak self] in
410
- guard let self = self else { return }
411
- if self.discoveredDevices.isEmpty && self.isScanning {
412
- FlirLogger.logError(.discovery, "Discovery timed out with no devices")
413
- self.discovery?.stop()
414
- self.isScanning = false
415
- DispatchQueue.main.async {
416
- // Notify native listeners that discovery finished with no devices
417
- self.delegate?.onDevicesFound(self.discoveredDevices)
418
- // Emit a state change so JS can set noDeviceFound = true
408
+ let timeoutWork = DispatchWorkItem { [weak self] in
409
+ guard let self = self, self.isScanning else { return }
410
+ FlirLogger.log(.discovery, "⏱ Discovery timeout reached - stopping scan")
411
+ self.discovery?.stop()
412
+ self.isScanning = false
413
+
414
+ // Emit final device list and state
415
+ DispatchQueue.main.async {
416
+ self.delegate?.onDevicesFound(self.discoveredDevices)
417
+ if self.discoveredDevices.isEmpty {
419
418
  self.emitStateChange("no_device_found")
420
- self.delegate?.onError("Discovery timed out")
419
+ self.delegate?.onError("No FLIR devices found")
421
420
  }
422
421
  }
423
422
  }
424
- discoveryTimeoutWorkItem = discWork
425
- DispatchQueue.main.asyncAfter(deadline: .now() + 8.0, execute: discWork)
423
+ discoveryTimeoutWorkItem = timeoutWork
424
+ DispatchQueue.main.asyncAfter(deadline: .now() + 8.0, execute: timeoutWork)
426
425
  #else
427
426
  FlirLogger.logError(.discovery, "FLIR SDK not available - discovery disabled")
428
427
  delegate?.onError("FLIR SDK not available")
@@ -458,6 +457,10 @@ import ThermalSDK
458
457
  #if FLIR_ENABLED
459
458
  discoveryTimeoutWorkItem?.cancel()
460
459
  discoveryTimeoutWorkItem = nil
460
+ discovery?.stop()
461
+ isScanning = false
462
+ FlirLogger.log(.discovery, "Discovery stopped")
463
+ #endif
461
464
  }
462
465
 
463
466
  // MARK: - Connection
@@ -483,8 +486,6 @@ import ThermalSDK
483
486
  }
484
487
 
485
488
  #if FLIR_ENABLED
486
- private var identityMap: [String: FLIRIdentity] = [:]
487
-
488
489
  private func findIdentity(for deviceId: String) -> FLIRIdentity? {
489
490
  return identityMap[deviceId]
490
491
  }
@@ -508,29 +509,19 @@ import ThermalSDK
508
509
  return
509
510
  }
510
511
 
511
- // Fail-fast authentication for generic/network cameras (prevent indefinite blocking)
512
+ // Handle authentication for generic cameras (network cameras)
512
513
  if identity.cameraType() == .generic {
513
514
  FlirLogger.log(.connection, "Generic/network camera - starting authentication...")
514
515
  let certName = getCertificateName()
515
- var authStatus = FLIRAuthenticationStatus.pending
516
- var attempts = 0
517
- let maxAttempts = 10
518
- while authStatus == .pending && attempts < maxAttempts {
519
- authStatus = cam.authenticate(identity, trustedConnectionName: certName)
520
- if authStatus == .pending {
521
- FlirLogger.log(.connection, "Authentication pending... attempt \(attempts + 1)")
516
+ var status = FLIRAuthenticationStatus.pending
517
+ while status == .pending {
518
+ status = cam.authenticate(identity, trustedConnectionName: certName)
519
+ if status == .pending {
520
+ FlirLogger.log(.connection, "Waiting for camera authentication approval...")
522
521
  Thread.sleep(forTimeInterval: 1.0)
523
- attempts += 1
524
522
  }
525
523
  }
526
- FlirLogger.log(.connection, "Authentication status: \(authStatus.rawValue)")
527
- if authStatus == .pending {
528
- FlirLogger.logError(.connection, "Authentication timed out for device: \(identity.deviceId())")
529
- DispatchQueue.main.async { [weak self] in
530
- self?.delegate?.onError("Authentication timed out")
531
- }
532
- return
533
- }
524
+ FlirLogger.log(.connection, "Authentication status: \(status.rawValue)")
534
525
  }
535
526
 
536
527
  do {
@@ -544,7 +535,6 @@ import ThermalSDK
544
535
  FlirLogger.log(.connection, "Step 2: Connecting to device...")
545
536
  try cam.connect()
546
537
  FlirLogger.log(.connection, "✅ Connected successfully to: \(identity.deviceId())")
547
-
548
538
 
549
539
  // Update state
550
540
  connectedIdentity = identity
@@ -642,9 +632,10 @@ import ThermalSDK
642
632
  FlirLogger.log(.streaming, "Stopping stream...")
643
633
 
644
634
  #if FLIR_ENABLED
645
- // Cancel any pending watchdog to avoid false positives after an explicit stop
646
- streamWatchdogWorkItem?.cancel()
647
- streamWatchdogWorkItem = nil
635
+ stream?.stop()
636
+ stream = nil
637
+ streamer = nil
638
+ _isStreaming = false
648
639
  emitStateChange("connected")
649
640
  FlirLogger.log(.streaming, "Stream stopped")
650
641
  #endif
@@ -669,28 +660,6 @@ import ThermalSDK
669
660
  _isStreaming = true
670
661
  emitStateChange("streaming")
671
662
  FlirLogger.log(.streaming, "✅ Stream started successfully (thermal=\(newStream.isThermal))")
672
-
673
- // Schedule a short watchdog to detect silent no-frame scenarios.
674
- streamWatchdogWorkItem?.cancel()
675
- let watch = DispatchWorkItem { [weak self] in
676
- guard let self = self else { return }
677
- if self._latestImage == nil {
678
- FlirLogger.logError(.streaming, "No frames received within watchdog period after stream start")
679
- // Attempt a soft restart
680
- do {
681
- self.stream?.stop()
682
- try self.stream?.start()
683
- FlirLogger.log(.streaming, "Attempted stream restart")
684
- } catch {
685
- FlirLogger.logError(.streaming, "Stream restart failed", error: error)
686
- DispatchQueue.main.async {
687
- self.delegate?.onError("No frames received after starting stream")
688
- }
689
- }
690
- }
691
- }
692
- streamWatchdogWorkItem = watch
693
- DispatchQueue.main.asyncAfter(deadline: .now() + 3.0, execute: watch)
694
663
  } catch {
695
664
  FlirLogger.logError(.streaming, "Stream start failed", error: error)
696
665
  stream = nil
@@ -970,35 +939,44 @@ extension FlirManager: FLIRDiscoveryEventDelegate {
970
939
  public func discoveryError(_ error: String, netServiceError nsnetserviceserror: Int32, on iface: FLIRCommunicationInterface) {
971
940
  FlirLogger.logError(.discovery, "Discovery error: \(error) (code=\(nsnetserviceserror)) on interface: \(iface)")
972
941
 
973
- // Stop scanning and emit current device list so UI can recover
942
+ // Stop scanning and cancel timeout on error
943
+ discoveryTimeoutWorkItem?.cancel()
944
+ discoveryTimeoutWorkItem = nil
974
945
  discovery?.stop()
975
946
  isScanning = false
947
+
948
+ // Emit current device list (could be empty) so RN/UI can recover
976
949
  DispatchQueue.main.async { [weak self] in
977
- self?.delegate?.onDevicesFound(self?.discoveredDevices ?? [])
978
- self?.delegate?.onError("Discovery error: \(error)")
950
+ guard let self = self else { return }
951
+ self.delegate?.onDevicesFound(self.discoveredDevices)
952
+ self.delegate?.onError("Discovery error: \(error)")
979
953
  }
980
954
  }
981
955
 
982
956
  public func discoveryFinished(_ iface: FLIRCommunicationInterface) {
983
957
  FlirLogger.log(.discovery, "Discovery finished on interface: \(iface)")
984
958
  isScanning = false
985
- // Emit final list so consumers can update even if discovery ended quietly
959
+
960
+ // Cancel timeout since discovery finished normally
961
+ discoveryTimeoutWorkItem?.cancel()
962
+ discoveryTimeoutWorkItem = nil
963
+
964
+ // CRITICAL: Emit final device list so RN layer doesn't hang waiting for results
986
965
  DispatchQueue.main.async { [weak self] in
987
966
  guard let self = self else { return }
988
967
  self.delegate?.onDevicesFound(self.discoveredDevices)
989
- // If no devices were found, emit an explicit no_device_found state
968
+ // If no devices were found, emit explicit state so UI can show "no devices"
990
969
  if self.discoveredDevices.isEmpty {
991
970
  self.emitStateChange("no_device_found")
992
971
  }
993
972
  }
994
- // Cancel the timeout if discovery finished normally
995
- discoveryTimeoutWorkItem?.cancel()
996
- discoveryTimeoutWorkItem = nil
997
973
  }
998
974
  }
975
+ #endif
999
976
 
1000
977
  // MARK: - FLIRDataReceivedDelegate
1001
978
 
979
+ #if FLIR_ENABLED
1002
980
  extension FlirManager: FLIRDataReceivedDelegate {
1003
981
  public func onDisconnected(_ camera: FLIRCamera, withError error: Error?) {
1004
982
  FlirLogger.logError(.disconnect, "Camera disconnected callback", error: error)
@@ -1020,9 +998,12 @@ extension FlirManager: FLIRDataReceivedDelegate {
1020
998
  }
1021
999
  }
1022
1000
  }
1001
+ #endif
1023
1002
 
1024
1003
  // MARK: - FLIRStreamDelegate
1025
1004
 
1005
+ #if FLIR_ENABLED
1006
+
1026
1007
  extension FlirManager: FLIRStreamDelegate {
1027
1008
  public func onError(_ error: Error) {
1028
1009
  FlirLogger.logError(.streaming, "Stream error", error: error)
@@ -155,14 +155,9 @@ RCT_EXPORT_METHOD(addListener : (NSString *)eventName) {
155
155
  if (manager) {
156
156
  NSArray *devices = ((NSArray * (*)(id, SEL))
157
157
  objc_msgSend)(manager, sel_registerName("getDiscoveredDevices"));
158
- // Re-emit even if the device list is empty so JS listeners can
159
- // observe a 'no devices found' state and avoid hanging while waiting.
160
- if (devices) {
161
- NSLog(@"[FlirModule] addListener - re-emitting discovered devices (count=%lu)", (unsigned long)devices.count);
158
+ if (devices && devices.count > 0) {
159
+ NSLog(@"[FlirModule] addListener - re-emitting %lu discovered devices", (unsigned long)devices.count);
162
160
  [self onDevicesFound:devices];
163
- } else {
164
- NSLog(@"[FlirModule] addListener - no discovered devices available to re-emit");
165
- [self onDevicesFound:@[]];
166
161
  }
167
162
  }
168
163
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.2.4",
3
+ "version": "2.2.6",
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",