ilabs-flir 2.4.7 → 2.4.8

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/Flir.podspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'Flir'
3
- s.version = '2.2.31'
3
+ s.version = '2.4.8'
4
4
  s.summary = 'FLIR Thermal SDK React Native - Bundled via postinstall'
5
5
  s.description = <<-DESC
6
6
  A React Native wrapper for the FLIR Thermal SDK, providing thermal imaging
@@ -387,6 +387,8 @@ object FlirManager {
387
387
  }
388
388
  ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
389
389
  .emit("FlirDeviceConnected", params)
390
+ ctx.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
391
+ .emit("FlirStateChanged", params)
390
392
  } catch (e: Exception) { }
391
393
  }
392
394
 
@@ -86,7 +86,37 @@ public class FlirSdkManager {
86
86
  private volatile SnapshotCallback snapshotCallback;
87
87
 
88
88
  private FlirSdkManager(Context context) {
89
- this.context = context.getApplicationContext();
89
+ // We wrap the Application context to intercept and swallow IllegalArgumentException
90
+ // during unregisterReceiver. The FLIR SDK has a bug in WifiScanner where it attempts
91
+ // to unregister a receiver that wasn't registered, which bubbles up to JNI and crashes
92
+ // the app via std::terminate.
93
+ this.context = new android.content.ContextWrapper(context.getApplicationContext()) {
94
+ @Override
95
+ public Context getApplicationContext() {
96
+ return this;
97
+ }
98
+
99
+ @Override
100
+ public android.content.Intent registerReceiver(android.content.BroadcastReceiver receiver, android.content.IntentFilter filter) {
101
+ try {
102
+ return super.registerReceiver(receiver, filter);
103
+ } catch (Exception e) {
104
+ Log.w(TAG, "Suppressed registerReceiver error: " + e.getMessage());
105
+ return null;
106
+ }
107
+ }
108
+
109
+ @Override
110
+ public void unregisterReceiver(android.content.BroadcastReceiver receiver) {
111
+ try {
112
+ super.unregisterReceiver(receiver);
113
+ } catch (IllegalArgumentException e) {
114
+ Log.w(TAG, "Suppressed SDK crash: Receiver not registered: " + e.getMessage());
115
+ } catch (Exception e) {
116
+ Log.w(TAG, "Suppressed unregisterReceiver error: " + e.getMessage());
117
+ }
118
+ }
119
+ };
90
120
  }
91
121
 
92
122
  public static synchronized FlirSdkManager getInstance(Context context) {
@@ -106,8 +136,51 @@ public class FlirSdkManager {
106
136
  if (isInitialized.compareAndSet(false, true)) {
107
137
  Log.d(TAG, "Initializing FLIR SDK (async)...");
108
138
 
139
+ // Register a main thread looper protector to completely safeguard against WifiScanner supplicant receiver crashes
140
+ new android.os.Handler(android.os.Looper.getMainLooper()).post(new Runnable() {
141
+ @Override
142
+ public void run() {
143
+ while (true) {
144
+ try {
145
+ android.os.Looper.loop();
146
+ } catch (Throwable t) {
147
+ String msg = t.getMessage();
148
+ boolean isSuppressed = false;
149
+
150
+ // Check if this is the notorious FLIR supplicant receiver crash
151
+ if (t instanceof RuntimeException && t.getCause() instanceof IllegalArgumentException) {
152
+ String causeMsg = t.getCause().getMessage();
153
+ if (causeMsg != null && (causeMsg.contains("Receiver not registered") || causeMsg.contains("WifiScanner"))) {
154
+ isSuppressed = true;
155
+ }
156
+ } else if (msg != null && (msg.contains("Receiver not registered") || msg.contains("WifiScanner") || msg.contains("supplicant.STATE_CHANGE"))) {
157
+ isSuppressed = true;
158
+ }
159
+
160
+ if (isSuppressed) {
161
+ Log.w(TAG, "🔒 [FLIR LOOPER PROTECTOR] Intercepted and swallowed broadcast receiver crash: " + t.getMessage());
162
+ } else {
163
+ // For all other unexpected exceptions, forward to the standard uncaught exception handler
164
+ Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
165
+ if (handler != null) {
166
+ handler.uncaughtException(Thread.currentThread(), t);
167
+ }
168
+ break;
169
+ }
170
+ }
171
+ }
172
+ }
173
+ });
174
+
109
175
  executor.execute(() -> {
110
176
  try {
177
+ try {
178
+ System.loadLibrary("c++_flir");
179
+ Log.d(TAG, "Successfully loaded legacy libc++_flir.so for FLIR SDK.");
180
+ } catch (UnsatisfiedLinkError e) {
181
+ Log.e(TAG, "Could not explicitly load libc++_flir.so. FLIR initialization might fail.", e);
182
+ }
183
+
111
184
  ThermalSdkAndroid.init(context);
112
185
 
113
186
  // Small delay to ensure JNI linkage is stable
@@ -203,54 +203,39 @@ import ThermalSDK
203
203
  let camType = identity.cameraType()
204
204
  NSLog("[FlirManager] Camera type: \(camType.rawValue), interface: \(iface.rawValue)")
205
205
 
206
- // ── AUTHENTICATE for network cameras ──
207
- // Official FLIR CameraConnector sample checks .generic camera type,
208
- // but FLIR One Edge Pro over network may report a different type.
209
- // Check BOTH: camera type == .generic OR interface contains .network/.flirOneWireless
210
- let needsAuth = (camType == .generic) || iface.contains(.network) || iface.contains(.flirOneWireless)
206
+ let isNetwork = (camType == .generic) || iface.contains(.network)
211
207
 
212
- if needsAuth {
213
- NSLog("[FlirManager] Network camera detected authenticating...")
214
-
215
- // Use UUID-based persistent certificate name (matches FLIR sample).
216
- // The camera has a bug where re-auth with a different name can conflict,
217
- // so we generate a UUID once and persist it in UserDefaults.
218
- let certName = self.getPersistentCertificateName()
219
- NSLog("[FlirManager] Using certificate name: \(certName)")
208
+ // ── STEP 1: INITIAL TRUST HANDSHAKE (Network Cameras Only) ──
209
+ // DiscoverySampleSwift shows that for network cameras we first authenticate
210
+ // with a user-friendly device/app name to trigger the "Trust this device" prompt.
211
+ if isNetwork {
212
+ NSLog("[FlirManager] Network camera detected initiating initial trust request...")
213
+ let appName = Bundle.main.infoDictionary?[kCFBundleNameKey as String] as? String ?? "AppFactory"
214
+ let trustName = "\(UIDevice.current.name) \(appName)"
220
215
 
221
216
  var status = FLIRAuthenticationStatus.pending
222
217
  var attempts = 0
223
- let maxAttempts = 30 // ~30 seconds timeout
218
+ let maxAttempts = 15 // 15 seconds window for initial prompt
224
219
 
225
220
  while status == .pending && attempts < maxAttempts {
226
- status = cam.authenticate(identity, trustedConnectionName: certName)
227
- NSLog("[FlirManager] Auth attempt \(attempts + 1)/\(maxAttempts) status: \(status.rawValue)")
228
-
221
+ status = cam.authenticate(identity, trustedConnectionName: trustName)
222
+ NSLog("[FlirManager] Initial trust attempt \(attempts + 1)/\(maxAttempts) status: \(status.rawValue)")
229
223
  if status == .pending {
230
- // Camera waiting for user to press "Trust" on its screen
231
224
  Thread.sleep(forTimeInterval: 1.0)
232
225
  }
233
226
  attempts += 1
234
227
  }
235
-
236
- if status != .approved {
237
- NSLog("[FlirManager] Authentication failed/timed out: \(status.rawValue)")
238
- self.camera = nil
239
- DispatchQueue.main.async {
240
- self.emitStateChange("connection_failed")
241
- self.delegate?.onError("Camera authentication failed. Check the camera screen for a trust/approve prompt.")
242
- }
243
- return
244
- }
245
- NSLog("[FlirManager] Authentication approved ✅")
246
228
  }
247
229
 
248
- // ── PAIR ──
230
+ // ── STEP 2: PAIRING (Always do this before final authentication!) ──
231
+ // Both FLIROneCameraSwift and DiscoverySampleSwift require pairing to be established
232
+ // before the camera's communication channels are authenticated and connected.
249
233
  do {
234
+ NSLog("[FlirManager] Pairing camera with code 0...")
250
235
  try cam.pair(identity, code: 0)
251
- NSLog("[FlirManager] Pair succeeded")
236
+ NSLog("[FlirManager] Pairing succeeded")
252
237
  } catch {
253
- NSLog("[FlirManager] Pair failed: \(error)")
238
+ NSLog("[FlirManager] Pairing failed: \(error)")
254
239
  self._isConnected = false
255
240
  self.camera = nil
256
241
  DispatchQueue.main.async {
@@ -260,6 +245,37 @@ import ThermalSDK
260
245
  return
261
246
  }
262
247
 
248
+ // ── STEP 3: FINAL AUTHENTICATION ──
249
+ // Use a persistent UUID certificate for network/wireless cameras to avoid trust conflicts.
250
+ // For standard classic FLIR ONE/Edge devices, use "dummy" as per FLIROneCameraSwift ViewController.swift.
251
+ let certName = isNetwork ? self.getPersistentCertificateName() : "dummy"
252
+ NSLog("[FlirManager] Performing final authentication with name: \(certName)")
253
+
254
+ var status = FLIRAuthenticationStatus.pending
255
+ var attempts = 0
256
+ let maxAttempts = 30 // ~30 seconds timeout
257
+
258
+ while status == .pending && attempts < maxAttempts {
259
+ status = cam.authenticate(identity, trustedConnectionName: certName)
260
+ NSLog("[FlirManager] Final auth attempt \(attempts + 1)/\(maxAttempts) status: \(status.rawValue)")
261
+
262
+ if status == .pending {
263
+ Thread.sleep(forTimeInterval: 1.0)
264
+ }
265
+ attempts += 1
266
+ }
267
+
268
+ if status != .approved && isNetwork {
269
+ NSLog("[FlirManager] Final authentication failed or timed out: \(status.rawValue)")
270
+ self.camera = nil
271
+ DispatchQueue.main.async {
272
+ self.emitStateChange("connection_failed")
273
+ self.delegate?.onError("Camera authentication failed. Check the camera screen for a trust/approve prompt.")
274
+ }
275
+ return
276
+ }
277
+ NSLog("[FlirManager] Authentication approved ✅")
278
+
263
279
  // ── CONNECT ──
264
280
  do {
265
281
  try cam.connect()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.4.7",
3
+ "version": "2.4.8",
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",