ilabs-flir 2.2.9 → 2.2.10

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.
@@ -481,16 +481,31 @@ import ThermalSDK
481
481
 
482
482
  // MARK: - Connection
483
483
 
484
+ // Flag to prevent re-entrant connections
485
+ private var isConnecting = false
486
+
484
487
  @objc public func connectToDevice(_ deviceId: String) {
485
488
  FlirLogger.logConnectionAttempt(deviceId: deviceId)
486
489
 
487
490
  #if FLIR_ENABLED
488
- // Guard: if already connected, disconnect first
489
- if _isConnected {
490
- FlirLogger.log(.connection, "⚠️ Already connected - disconnecting first...")
491
+ // Guard: if already connecting, skip (like demo app's guard pattern)
492
+ guard !isConnecting else {
493
+ FlirLogger.log(.connection, "⚠️ Connection already in progress - skipping")
494
+ return
495
+ }
496
+
497
+ // Guard: if already connected to this device, skip (matches demo app)
498
+ if _isConnected && connectedDeviceId == deviceId {
499
+ FlirLogger.log(.connection, "⚠️ Already connected to this device - skipping")
500
+ return
501
+ }
502
+
503
+ // If connected to a different device, disconnect first but DON'T block
504
+ if _isConnected && connectedDeviceId != deviceId {
505
+ FlirLogger.log(.connection, "⚠️ Connected to different device - disconnecting first...")
491
506
  disconnect()
492
- // Give disconnect a moment to complete
493
- Thread.sleep(forTimeInterval: 0.5)
507
+ // Don't block! The new connection will proceed immediately
508
+ // The disconnect is async and will complete in the background
494
509
  }
495
510
 
496
511
  // Find the identity for this device
@@ -503,33 +518,14 @@ import ThermalSDK
503
518
  return
504
519
  }
505
520
 
506
- // Run connection on background thread with overall timeout
507
- let timeoutSeconds: Double = 15.0
508
- var connectionCompleted = false
509
- let connectionQueue = DispatchQueue.global(qos: .userInitiated)
510
-
511
- // Start connection attempt
512
- connectionQueue.async { [weak self] in
513
- self?.performConnection(identity: identity)
514
- connectionCompleted = true
515
- }
521
+ isConnecting = true
516
522
 
517
- // Monitor for timeout
518
- DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + timeoutSeconds) { [weak self] in
519
- guard let self = self, !connectionCompleted else { return }
520
-
521
- FlirLogger.logError(.connection, "❌ ⏱ CONNECTION TIMEOUT triggered after \(timeoutSeconds)s - aborting")
522
-
523
- // Force cleanup
524
- self.camera = nil
525
- self._isConnected = false
526
- self.connectedDeviceId = nil
527
-
528
- DispatchQueue.main.async {
529
- FlirLogger.log(.connection, "⏱ Emitting connection_failed to RN due to timeout")
530
- self.emitStateChange("connection_failed")
531
- self.delegate?.onError("Connection timeout - device not responding")
532
- }
523
+ // Run connection on background thread - simple pattern matching demo app
524
+ // Demo app uses: DispatchQueue.global().async { try pair(); try connect(); try stream.start() }
525
+ DispatchQueue.global(qos: .userInitiated).async { [weak self] in
526
+ guard let self = self else { return }
527
+ self.performConnection(identity: identity)
528
+ self.isConnecting = false
533
529
  }
534
530
  #else
535
531
  FlirLogger.logError(.connection, "FLIR SDK not available")
@@ -538,6 +534,7 @@ import ThermalSDK
538
534
  }
539
535
  #endif
540
536
  }
537
+
541
538
 
542
539
  #if FLIR_ENABLED
543
540
  private func findIdentity(for deviceId: String) -> FLIRIdentity? {
@@ -566,34 +563,28 @@ import ThermalSDK
566
563
  }
567
564
 
568
565
  // Handle authentication for generic cameras (network cameras)
566
+ // Note: This uses a polling approach since the SDK doesn't provide async callback
567
+ // Reduced polling frequency to minimize blocking
569
568
  if identity.cameraType() == .generic {
570
- FlirLogger.log(.connection, "Generic/network camera - AUTHENTICATION starting...")
569
+ FlirLogger.log(.connection, "Generic/network camera - attempting authentication...")
571
570
  let certName = getCertificateName()
572
- var status = FLIRAuthenticationStatus.pending
573
- var attempts = 0
574
- let maxAttempts = 10 // 10 seconds max
575
- let authStartTime = Date()
576
-
577
- while status == .pending && attempts < maxAttempts {
578
- status = cam.authenticate(identity, trustedConnectionName: certName)
579
- if status == .pending {
580
- FlirLogger.log(.connection, "Waiting for camera authentication approval... (\(attempts + 1)/\(maxAttempts))")
581
- Thread.sleep(forTimeInterval: 1.0)
582
- attempts += 1
583
- }
584
- }
571
+ let status = cam.authenticate(identity, trustedConnectionName: certName)
585
572
 
586
573
  if status == .pending {
587
- let authDuration = Date().timeIntervalSince(authStartTime)
588
- FlirLogger.logError(.connection, "Authentication TIMEOUT after \(authDuration)s (\(maxAttempts) attempts)")
574
+ // For pending auth, just log and continue - user needs to approve on camera
575
+ FlirLogger.log(.connection, "Authentication pending - camera may require approval")
576
+ // Don't block here - proceed with connection attempt
577
+ // If auth is required, the connect() call will fail appropriately
578
+ } else if status == .notUsed || status == .trusted {
579
+ FlirLogger.log(.connection, "✅ Authentication status: \(status.rawValue)")
580
+ } else {
581
+ FlirLogger.logError(.connection, "Authentication failed with status: \(status.rawValue)")
589
582
  DispatchQueue.main.async { [weak self] in
590
- self?.delegate?.onError("Camera authentication timeout - device may require approval")
583
+ self?.emitStateChange("connection_failed")
584
+ self?.delegate?.onError("Camera authentication failed")
591
585
  }
592
586
  return
593
587
  }
594
-
595
- let authDuration = Date().timeIntervalSince(authStartTime)
596
- FlirLogger.log(.connection, "✅ Authentication completed in \(authDuration)s - status: \(status.rawValue)")
597
588
  }
598
589
 
599
590
  do {
@@ -708,9 +699,15 @@ import ThermalSDK
708
699
  }
709
700
 
710
701
  @objc public func stopStream() {
702
+ #if FLIR_ENABLED
703
+ // Guard: if not streaming, skip to avoid redundant operations
704
+ guard _isStreaming || stream != nil else {
705
+ FlirLogger.log(.streaming, "Not streaming - skipping stopStream")
706
+ return
707
+ }
708
+
711
709
  FlirLogger.log(.streaming, "Stopping stream...")
712
710
 
713
- #if FLIR_ENABLED
714
711
  stream?.stop()
715
712
  stream = nil
716
713
  streamer = nil
@@ -743,6 +740,9 @@ import ThermalSDK
743
740
  FlirLogger.logError(.streaming, "Stream start failed", error: error)
744
741
  stream = nil
745
742
  streamer = nil
743
+ _isStreaming = false
744
+ // Emit state change so RN knows streaming failed
745
+ emitStateChange("connected") // Fall back to connected (not streaming)
746
746
  delegate?.onError("Stream start failed: \(error.localizedDescription)")
747
747
  }
748
748
  }
@@ -751,9 +751,15 @@ import ThermalSDK
751
751
  // MARK: - Disconnect
752
752
 
753
753
  @objc public func disconnect() {
754
+ #if FLIR_ENABLED
755
+ // Guard: if already disconnected, do nothing (prevents re-entrant issues)
756
+ guard _isConnected || stream != nil || camera != nil else {
757
+ FlirLogger.log(.disconnect, "Already disconnected - skipping")
758
+ return
759
+ }
760
+
754
761
  FlirLogger.log(.disconnect, "Disconnecting...")
755
762
 
756
- #if FLIR_ENABLED
757
763
  // Stop battery polling
758
764
  stopBatteryPolling()
759
765
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.2.9",
3
+ "version": "2.2.10",
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",