ilabs-flir 2.1.0 → 2.1.2

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.
@@ -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
@@ -169,21 +202,70 @@ RCT_EXPORT_METHOD(startDiscovery:(RCTPromiseResolveBlock)resolve
169
202
  self.isScanning = YES;
170
203
  [self.discoveredDevices removeAllObjects];
171
204
  [self.identityMap removeAllObjects];
205
+
206
+ // Always expose emulator options to JS/UI so the user can connect even when
207
+ // physical devices are not present.
208
+ NSDictionary *emuOne = @{
209
+ @"id": @"emu:FLIR_ONE",
210
+ @"name": @"FLIR One Emulator",
211
+ @"communicationType": @"EMULATOR",
212
+ @"isEmulator": @(YES)
213
+ };
214
+ NSDictionary *emuEdge = @{
215
+ @"id": @"emu:FLIR_ONE_EDGE",
216
+ @"name": @"FLIR One Edge Emulator",
217
+ @"communicationType": @"EMULATOR",
218
+ @"isEmulator": @(YES)
219
+ };
220
+ [self.discoveredDevices addObjectsFromArray:@[ emuOne, emuEdge ]];
221
+
222
+ NSDictionary *initialDevicesBody = @{
223
+ @"devices": self.discoveredDevices,
224
+ @"count": @(self.discoveredDevices.count)
225
+ };
226
+ [[FlirEventEmitter shared] sendDeviceEvent:@"FlirDevicesFound" body:initialDevicesBody];
172
227
 
173
228
  if (!self.discovery) {
174
229
  self.discovery = [[FLIRDiscovery alloc] init];
175
230
  self.discovery.delegate = self;
176
231
  }
177
232
 
178
- // 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).
179
236
  FLIRCommunicationInterface interfaces = FLIRCommunicationInterfaceLightning |
180
- FLIRCommunicationInterfaceNetwork |
181
237
  FLIRCommunicationInterfaceFlirOneWireless |
182
- 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
+ }
183
245
  [self.discovery start:interfaces];
184
246
 
185
247
  [self emitStateChange:@"discovering"];
186
248
  RCTLogInfo(@"[FlirModule] Discovery started");
249
+
250
+ __weak typeof(self) weakSelf = self;
251
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
252
+ __strong typeof(self) strongSelf = weakSelf;
253
+ if (!strongSelf) return;
254
+ if (!strongSelf.isScanning || strongSelf.isConnected) return;
255
+
256
+ BOOL hasRealDevice = NO;
257
+ for (NSDictionary *dev in strongSelf.discoveredDevices) {
258
+ NSString *did = dev[@"id"];
259
+ if (did.length > 0 && ![did hasPrefix:@"emu:"]) {
260
+ hasRealDevice = YES;
261
+ break;
262
+ }
263
+ }
264
+ if (!hasRealDevice) {
265
+ [strongSelf emitStateChange:@"no_device_found"];
266
+ }
267
+ });
268
+
187
269
  resolve(@(YES));
188
270
  });
189
271
  #else
@@ -219,6 +301,36 @@ RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
219
301
  rejecter:(RCTPromiseRejectBlock)reject) {
220
302
  #if FLIR_SDK_AVAILABLE
221
303
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
304
+ // Synthetic emulator ids exposed during discovery.
305
+ if ([deviceId hasPrefix:@"emu:"]) {
306
+ NSString *typePart = [deviceId substringFromIndex:4];
307
+ FLIRCameraType cameraType = FLIRCameraType_flirOne;
308
+ if ([typePart.lowercaseString containsString:@"edge"]) {
309
+ cameraType = FLIRCameraType_flirOneEdge;
310
+ }
311
+
312
+ FLIRIdentity *emulatorIdentity = [[FLIRIdentity alloc] initWithEmulatorType:cameraType];
313
+ if (!emulatorIdentity) {
314
+ dispatch_async(dispatch_get_main_queue(), ^{
315
+ reject(@"ERR_EMULATOR_INIT", @"Failed to create emulator identity", nil);
316
+ });
317
+ return;
318
+ }
319
+
320
+ self.identityMap[[emulatorIdentity deviceId]] = emulatorIdentity;
321
+
322
+ [self performConnectionWithIdentity:emulatorIdentity completion:^(BOOL success, NSError *error) {
323
+ dispatch_async(dispatch_get_main_queue(), ^{
324
+ if (success) {
325
+ resolve(@(YES));
326
+ } else {
327
+ reject(@"ERR_CONNECTION_FAILED", error.localizedDescription ?: @"Connection failed", error);
328
+ }
329
+ });
330
+ }];
331
+ return;
332
+ }
333
+
222
334
  FLIRIdentity *identity = self.identityMap[deviceId];
223
335
  if (!identity) {
224
336
  dispatch_async(dispatch_get_main_queue(), ^{
@@ -269,7 +381,15 @@ RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
269
381
  if (connected) {
270
382
  self.connectedIdentity = identity;
271
383
  self.connectedDeviceId = [identity deviceId];
272
- self.connectedDeviceName = [identity deviceId];
384
+ NSString *displayName = [identity deviceId];
385
+ if ([identity communicationInterface] == FLIRCommunicationInterfaceEmulator) {
386
+ if ([identity cameraType] == FLIRCameraType_flirOneEdge || [identity cameraType] == FLIRCameraType_flirOneEdgePro) {
387
+ displayName = @"FLIR One Edge Emulator";
388
+ } else {
389
+ displayName = @"FLIR One Emulator";
390
+ }
391
+ }
392
+ self.connectedDeviceName = displayName;
273
393
  self.isConnected = YES;
274
394
 
275
395
  RCTLogInfo(@"[FlirModule] Connected to: %@", [identity deviceId]);
@@ -284,7 +404,6 @@ RCT_EXPORT_METHOD(connectToDevice:(NSString *)deviceId
284
404
 
285
405
  dispatch_async(dispatch_get_main_queue(), ^{
286
406
  [self emitDeviceConnected];
287
- [self emitStateChange:@"connected"];
288
407
  });
289
408
 
290
409
  if (completion) completion(YES, nil);
@@ -613,23 +732,20 @@ RCT_EXPORT_METHOD(isPreferSdkRotation:(RCTPromiseResolveBlock)resolve
613
732
  #pragma mark - Helper Methods
614
733
 
615
734
  - (void)emitDeviceConnected {
616
- BOOL isEmu = [self.connectedDeviceName.lowercaseString containsString:@"emulator"];
617
-
618
- NSDictionary *body = @{
619
- @"identity": @{
620
- @"deviceId": self.connectedDeviceId ?: @"Unknown",
621
- @"isEmulator": @(isEmu)
622
- },
623
- @"deviceType": isEmu ? @"emulator" : @"device",
624
- @"isEmulator": @(isEmu),
625
- @"state": @"connected"
626
- };
627
-
628
- [[FlirEventEmitter shared] sendDeviceEvent:@"FlirDeviceConnected" body:body];
735
+ [self emitStateChange:@"connected"];
629
736
  }
630
737
 
631
738
  - (void)emitStateChange:(NSString *)state {
632
- BOOL isEmu = [self.connectedDeviceName.lowercaseString containsString:@"emulator"];
739
+ BOOL isEmu = NO;
740
+ #if FLIR_SDK_AVAILABLE
741
+ if (self.connectedIdentity) {
742
+ isEmu = ([self.connectedIdentity communicationInterface] == FLIRCommunicationInterfaceEmulator);
743
+ } else {
744
+ isEmu = [self.connectedDeviceName.lowercaseString containsString:@"emulator"];
745
+ }
746
+ #else
747
+ isEmu = [self.connectedDeviceName.lowercaseString containsString:@"emulator"];
748
+ #endif
633
749
 
634
750
  NSDictionary *body = @{
635
751
  @"state": state,
@@ -637,9 +753,17 @@ RCT_EXPORT_METHOD(isPreferSdkRotation:(RCTPromiseResolveBlock)resolve
637
753
  @"isStreaming": @(self.isStreaming),
638
754
  @"isEmulator": @(isEmu),
639
755
  @"deviceName": self.connectedDeviceName ?: @"",
640
- @"deviceId": self.connectedDeviceId ?: @""
756
+ @"deviceId": self.connectedDeviceId ?: @"",
757
+ @"identity": @{
758
+ @"deviceId": self.connectedDeviceId ?: @"",
759
+ @"isEmulator": @(isEmu)
760
+ }
641
761
  };
642
-
762
+
763
+ // App JS listens for FlirDeviceConnected state transitions.
764
+ [[FlirEventEmitter shared] sendDeviceEvent:@"FlirDeviceConnected" body:body];
765
+
766
+ // Keep legacy event for backwards compatibility.
643
767
  [[FlirEventEmitter shared] sendDeviceEvent:@"FlirStateChanged" body:body];
644
768
  }
645
769
 
@@ -737,8 +861,15 @@ RCT_EXPORT_METHOD(isPreferSdkRotation:(RCTPromiseResolveBlock)resolve
737
861
  }
738
862
 
739
863
  - (void)discoveryError:(NSString *)error netServiceError:(int)nsnetserviceserror on:(FLIRCommunicationInterface)iface {
864
+ // Network discovery failures are expected when Local Network permission is missing/denied.
865
+ // Do not surface those as fatal errors; keep USB/BLE discovery running.
866
+ if ((iface & FLIRCommunicationInterfaceNetwork) == FLIRCommunicationInterfaceNetwork) {
867
+ RCTLogInfo(@"[FlirModule] Network discovery error (suppressed): %@ (%d)", error, nsnetserviceserror);
868
+ return;
869
+ }
870
+
740
871
  RCTLogError(@"[FlirModule] Discovery error: %@ (%d)", error, nsnetserviceserror);
741
-
872
+
742
873
  dispatch_async(dispatch_get_main_queue(), ^{
743
874
  [[FlirEventEmitter shared] sendDeviceEvent:@"FlirError" body:@{
744
875
  @"error": error ?: @"Unknown discovery error",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilabs-flir",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
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",