ilabs-flir 2.0.4 → 2.0.5

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.
Files changed (35) hide show
  1. package/Flir.podspec +139 -139
  2. package/README.md +1066 -1066
  3. package/android/Flir/build.gradle.kts +72 -72
  4. package/android/Flir/src/main/AndroidManifest.xml +45 -45
  5. package/android/Flir/src/main/java/flir/android/FlirCommands.java +136 -136
  6. package/android/Flir/src/main/java/flir/android/FlirFrameCache.kt +6 -6
  7. package/android/Flir/src/main/java/flir/android/FlirManager.kt +476 -476
  8. package/android/Flir/src/main/java/flir/android/FlirModule.kt +257 -257
  9. package/android/Flir/src/main/java/flir/android/FlirPackage.kt +18 -18
  10. package/android/Flir/src/main/java/flir/android/FlirSDKLoader.kt +74 -74
  11. package/android/Flir/src/main/java/flir/android/FlirSdkManager.java +583 -583
  12. package/android/Flir/src/main/java/flir/android/FlirStatus.kt +12 -12
  13. package/android/Flir/src/main/java/flir/android/FlirView.kt +48 -48
  14. package/android/Flir/src/main/java/flir/android/FlirViewManager.kt +13 -13
  15. package/app.plugin.js +381 -381
  16. package/expo-module.config.json +5 -5
  17. package/ios/Flir/src/Flir-Bridging-Header.h +34 -34
  18. package/ios/Flir/src/FlirEventEmitter.h +25 -25
  19. package/ios/Flir/src/FlirEventEmitter.m +63 -63
  20. package/ios/Flir/src/FlirManager.swift +599 -599
  21. package/ios/Flir/src/FlirModule.h +17 -17
  22. package/ios/Flir/src/FlirModule.m +713 -713
  23. package/ios/Flir/src/FlirPreviewView.h +13 -13
  24. package/ios/Flir/src/FlirPreviewView.m +171 -171
  25. package/ios/Flir/src/FlirState.h +68 -68
  26. package/ios/Flir/src/FlirState.m +135 -135
  27. package/ios/Flir/src/FlirViewManager.h +16 -16
  28. package/ios/Flir/src/FlirViewManager.m +27 -27
  29. package/package.json +72 -71
  30. package/react-native.config.js +14 -14
  31. package/scripts/fetch-binaries.js +47 -5
  32. package/sdk-manifest.json +50 -50
  33. package/src/index.d.ts +63 -63
  34. package/src/index.js +7 -7
  35. package/src/index.ts +6 -6
package/app.plugin.js CHANGED
@@ -1,381 +1,381 @@
1
- const {
2
- withInfoPlist,
3
- withAndroidManifest,
4
- withDangerousMod,
5
- withEntitlementsPlist,
6
- createRunOncePlugin,
7
- } = require('@expo/config-plugins');
8
- const fs = require('fs');
9
- const path = require('path');
10
-
11
- /**
12
- * FLIR Thermal SDK Config Plugin
13
- *
14
- * Automatically adds required iOS Info.plist and Android AndroidManifest.xml
15
- * entries for FLIR device support. This eliminates the need for manual editing.
16
- *
17
- * Usage in app.json:
18
- * {
19
- * "plugins": ["ilabs-flir"]
20
- * }
21
- *
22
- * Or with options:
23
- * {
24
- * "plugins": [
25
- * ["ilabs-flir", {
26
- * "lightningOnly": true, // iOS ONLY: Skip network/WiFi permissions
27
- * "disableNetworkPermissions": true, // Same as lightningOnly (iOS only)
28
- * "bluetoothAlwaysUsageDescription": "Custom description",
29
- * "localNetworkUsageDescription": "Custom description"
30
- * }]
31
- * ]
32
- * }
33
- *
34
- * FLAGS:
35
- * - lightningOnly / disableNetworkPermissions (iOS ONLY):
36
- * Set to true to skip Local Network, Bonjour, and Bluetooth permissions on iOS.
37
- * Use this if you only need Lightning-connected FLIR ONE devices
38
- * and don't have a paid Apple Developer license.
39
- * NOTE: This flag does NOT affect Android - Android always gets all permissions.
40
- */
41
-
42
- // External Accessory Protocols for FLIR ONE Lightning devices
43
- const EXTERNAL_ACCESSORY_PROTOCOLS = [
44
- 'com.flir.rosebud.config',
45
- 'com.flir.rosebud.frame',
46
- 'com.flir.rosebud.fileio',
47
- ];
48
-
49
- // Bonjour services for FLIR network discovery
50
- const BONJOUR_SERVICES = [
51
- '_flir._tcp',
52
- '_http._tcp',
53
- ];
54
-
55
- // Default permission descriptions
56
- const DEFAULT_DESCRIPTIONS = {
57
- bluetoothAlways: 'This app requires Bluetooth to connect to FLIR ONE Edge and other wireless thermal cameras via Bluetooth Low Energy.',
58
- bluetoothPeripheral: 'This app uses Bluetooth to communicate with FLIR thermal imaging devices.',
59
- localNetwork: 'This app needs local network access to discover and connect to FLIR thermal cameras on your WiFi network.',
60
- camera: 'This app uses the camera to capture photos and video alongside thermal imaging.',
61
- accessoryConnection: 'This app connects to FLIR ONE thermal camera accessories.',
62
- };
63
-
64
- /**
65
- * Adds FLIR-specific Info.plist entries for iOS
66
- */
67
- const withFlirInfoPlist = (config, props = {}) => {
68
- return withInfoPlist(config, (config) => {
69
- const infoPlist = config.modResults;
70
-
71
- // Check if network permissions should be skipped
72
- const skipNetworkPermissions = props.lightningOnly === true ||
73
- props.disableNetworkPermissions === true;
74
-
75
- if (skipNetworkPermissions) {
76
- console.log('[ilabs-flir] ⚠️ lightningOnly mode: Skipping network/WiFi permissions');
77
- }
78
-
79
- // =========================================================================
80
- // EXTERNAL ACCESSORY PROTOCOLS
81
- // Required for FLIR ONE devices connected via Lightning port
82
- // =========================================================================
83
- if (!infoPlist.UISupportedExternalAccessoryProtocols) {
84
- infoPlist.UISupportedExternalAccessoryProtocols = [];
85
- }
86
-
87
- const existingProtocols = infoPlist.UISupportedExternalAccessoryProtocols;
88
- EXTERNAL_ACCESSORY_PROTOCOLS.forEach((protocol) => {
89
- if (!existingProtocols.includes(protocol)) {
90
- existingProtocols.push(protocol);
91
- }
92
- });
93
-
94
- // =========================================================================
95
- // BLUETOOTH PERMISSIONS
96
- // Required for FLIR ONE Edge, FLIR ONE Pro, and other BLE thermal cameras
97
- // Skip if lightningOnly mode (BLE requires network discovery in some cases)
98
- // =========================================================================
99
-
100
- if (!skipNetworkPermissions) {
101
- // iOS 13+ requires NSBluetoothAlwaysUsageDescription
102
- if (!infoPlist.NSBluetoothAlwaysUsageDescription) {
103
- infoPlist.NSBluetoothAlwaysUsageDescription =
104
- props.bluetoothAlwaysUsageDescription || DEFAULT_DESCRIPTIONS.bluetoothAlways;
105
- }
106
-
107
- // Older iOS versions (pre-13) require NSBluetoothPeripheralUsageDescription
108
- if (!infoPlist.NSBluetoothPeripheralUsageDescription) {
109
- infoPlist.NSBluetoothPeripheralUsageDescription =
110
- props.bluetoothPeripheralUsageDescription || DEFAULT_DESCRIPTIONS.bluetoothPeripheral;
111
- }
112
- }
113
-
114
- // =========================================================================
115
- // LOCAL NETWORK PERMISSION (iOS 14+)
116
- // Required for discovering FLIR cameras over WiFi
117
- // NOTE: This requires a PAID Apple Developer account to work properly
118
- // SKIP if lightningOnly mode
119
- // =========================================================================
120
- if (!skipNetworkPermissions) {
121
- if (!infoPlist.NSLocalNetworkUsageDescription) {
122
- infoPlist.NSLocalNetworkUsageDescription =
123
- props.localNetworkUsageDescription || DEFAULT_DESCRIPTIONS.localNetwork;
124
- }
125
-
126
- // =========================================================================
127
- // BONJOUR SERVICES
128
- // Required for mDNS/Bonjour network discovery of FLIR cameras
129
- // SKIP if lightningOnly mode
130
- // =========================================================================
131
- if (!infoPlist.NSBonjourServices) {
132
- infoPlist.NSBonjourServices = [];
133
- }
134
-
135
- const existingBonjour = infoPlist.NSBonjourServices;
136
- BONJOUR_SERVICES.forEach((service) => {
137
- if (!existingBonjour.includes(service)) {
138
- existingBonjour.push(service);
139
- }
140
- });
141
- }
142
-
143
- // =========================================================================
144
- // CAMERA PERMISSION (always added)
145
- // Required if using visual camera alongside thermal
146
- // =========================================================================
147
- if (!infoPlist.NSCameraUsageDescription) {
148
- infoPlist.NSCameraUsageDescription =
149
- props.cameraUsageDescription || DEFAULT_DESCRIPTIONS.camera;
150
- }
151
-
152
- // =========================================================================
153
- // ACCESSORY CONNECTION (iOS 16.4+)
154
- // Only needed for wireless accessories - skip in lightningOnly mode
155
- // =========================================================================
156
- if (!skipNetworkPermissions) {
157
- if (!infoPlist.NSAccessorySetupUsageDescription) {
158
- infoPlist.NSAccessorySetupUsageDescription =
159
- props.accessorySetupUsageDescription || DEFAULT_DESCRIPTIONS.accessoryConnection;
160
- }
161
- }
162
-
163
- // =========================================================================
164
- // BACKGROUND MODES (always added for Lightning accessory)
165
- // Enable external accessory communication in background
166
- // =========================================================================
167
- if (!infoPlist.UIBackgroundModes) {
168
- infoPlist.UIBackgroundModes = [];
169
- }
170
-
171
- const backgroundModes = infoPlist.UIBackgroundModes;
172
- if (!backgroundModes.includes('external-accessory')) {
173
- backgroundModes.push('external-accessory');
174
- }
175
-
176
- return config;
177
- });
178
- };
179
-
180
- /**
181
- * Adds FLIR-specific entitlements for iOS
182
- */
183
- const withFlirEntitlements = (config) => {
184
- return withEntitlementsPlist(config, (config) => {
185
- // Currently no special entitlements needed
186
- return config;
187
- });
188
- };
189
-
190
- /**
191
- * Adds FLIR-specific AndroidManifest.xml entries for Android
192
- * NOTE: The lightningOnly flag does NOT apply to Android.
193
- * Android always gets all permissions since it doesn't have the same
194
- * Apple Developer license restrictions for network discovery.
195
- */
196
- const withFlirAndroidManifest = (config, props = {}) => {
197
- return withAndroidManifest(config, (config) => {
198
- const androidManifest = config.modResults;
199
- const mainApplication = androidManifest.manifest;
200
-
201
- // Ensure uses-feature array exists
202
- if (!mainApplication['uses-feature']) {
203
- mainApplication['uses-feature'] = [];
204
- }
205
-
206
- // Ensure uses-permission array exists
207
- if (!mainApplication['uses-permission']) {
208
- mainApplication['uses-permission'] = [];
209
- }
210
-
211
- // Helper to add feature
212
- const addFeature = (name, required = false) => {
213
- const hasFeature = mainApplication['uses-feature'].some(
214
- (f) => f.$?.['android:name'] === name
215
- );
216
- if (!hasFeature) {
217
- mainApplication['uses-feature'].push({
218
- $: {
219
- 'android:name': name,
220
- 'android:required': required ? 'true' : 'false',
221
- },
222
- });
223
- }
224
- };
225
-
226
- // Helper to add permission
227
- const addPermission = (name) => {
228
- const hasPermission = mainApplication['uses-permission'].some(
229
- (p) => p.$?.['android:name'] === name
230
- );
231
- if (!hasPermission) {
232
- mainApplication['uses-permission'].push({
233
- $: { 'android:name': name },
234
- });
235
- }
236
- };
237
-
238
- // USB host feature for FLIR ONE USB devices
239
- addFeature('android.hardware.usb.host', false);
240
-
241
- // Camera permission
242
- addPermission('android.permission.CAMERA');
243
-
244
- // Bluetooth features for FLIR ONE Edge/Pro
245
- addFeature('android.hardware.bluetooth', false);
246
- addFeature('android.hardware.bluetooth_le', false);
247
-
248
- // WiFi feature for network cameras
249
- addFeature('android.hardware.wifi', false);
250
-
251
- // Network permissions (always added on Android)
252
- addPermission('android.permission.INTERNET');
253
- addPermission('android.permission.ACCESS_NETWORK_STATE');
254
- addPermission('android.permission.ACCESS_WIFI_STATE');
255
- addPermission('android.permission.BLUETOOTH');
256
- addPermission('android.permission.BLUETOOTH_ADMIN');
257
- addPermission('android.permission.BLUETOOTH_CONNECT');
258
- addPermission('android.permission.BLUETOOTH_SCAN');
259
- addPermission('android.permission.ACCESS_FINE_LOCATION'); // Required for BLE scanning
260
-
261
- return config;
262
- });
263
- };
264
-
265
- /**
266
- * Copies sdk-manifest.json to iOS project
267
- */
268
- const withFlirManifest = (config) => {
269
- return withDangerousMod(config, [
270
- 'ios',
271
- async (config) => {
272
- const src = path.join(__dirname, 'sdk-manifest.json');
273
- const dst = path.join(config.modRequest.platformProjectRoot, 'sdk-manifest.json');
274
- if (fs.existsSync(src)) {
275
- fs.copyFileSync(src, dst);
276
- }
277
- return config;
278
- },
279
- ]);
280
- };
281
-
282
- /**
283
- * Copies sdk-manifest.json to Android assets
284
- */
285
- const withFlirAndroidAssets = (config) => {
286
- return withDangerousMod(config, [
287
- 'android',
288
- async (config) => {
289
- const src = path.join(__dirname, 'sdk-manifest.json');
290
- const dst = path.join(config.modRequest.platformProjectRoot, 'app/src/main/assets/sdk-manifest.json');
291
- if (fs.existsSync(src)) {
292
- fs.mkdirSync(path.dirname(dst), { recursive: true });
293
- fs.copyFileSync(src, dst);
294
- }
295
- return config;
296
- },
297
- ]);
298
- };
299
-
300
- /**
301
- * Ensure the Flir Android Gradle module is included in generated projects.
302
- */
303
- const withFlirAndroidGradle = (config) => {
304
- return withDangerousMod(config, [
305
- 'android',
306
- async (config) => {
307
- try {
308
- const projectRoot = config.modRequest.platformProjectRoot;
309
- const settingsGradlePath = path.join(projectRoot, 'settings.gradle');
310
- const appBuildGradlePath = path.join(projectRoot, 'app', 'build.gradle');
311
-
312
- const moduleRelPath = '../node_modules/ilabs-flir/android/Flir';
313
- const includeSnippet = `\n// ilabs-flir: include Flir module\nif (new File(rootProject.projectDir, '${moduleRelPath}').exists()) {\n include ':Flir'\n project(':Flir').projectDir = new File(rootProject.projectDir, '${moduleRelPath}')\n}\n`;
314
-
315
- if (fs.existsSync(settingsGradlePath)) {
316
- let settingsTxt = fs.readFileSync(settingsGradlePath, 'utf8');
317
- if (!/include\s*':Flir'/.test(settingsTxt)) {
318
- fs.appendFileSync(settingsGradlePath, includeSnippet, 'utf8');
319
- }
320
- }
321
-
322
- if (fs.existsSync(appBuildGradlePath)) {
323
- let buildTxt = fs.readFileSync(appBuildGradlePath, 'utf8');
324
- if (!/project\('\:Flir'\)/.test(buildTxt)) {
325
- const depSnippet = `\n // ilabs-flir: include :Flir when available\n if (new File(rootDir.getParent(), '${moduleRelPath}').exists()) {\n implementation project(':Flir')\n }\n`;
326
-
327
- const depIndex = buildTxt.search(/\bdependencies\s*\{/);
328
- if (depIndex !== -1) {
329
- let depth = 0;
330
- let insertPos = -1;
331
- for (let i = depIndex; i < buildTxt.length; i++) {
332
- const ch = buildTxt[i];
333
- if (ch === '{') depth++;
334
- else if (ch === '}') {
335
- depth--;
336
- if (depth === 0) { insertPos = i; break; }
337
- }
338
- }
339
-
340
- if (insertPos !== -1) {
341
- buildTxt = buildTxt.slice(0, insertPos) + depSnippet + buildTxt.slice(insertPos);
342
- fs.writeFileSync(appBuildGradlePath, buildTxt, 'utf8');
343
- } else {
344
- fs.appendFileSync(appBuildGradlePath, '\n' + depSnippet, 'utf8');
345
- }
346
- } else {
347
- fs.appendFileSync(appBuildGradlePath, '\n' + 'dependencies {' + depSnippet + '\n}\n', 'utf8');
348
- }
349
- }
350
- }
351
- } catch (err) {
352
- console.warn('[flir-config-plugin] Failed to patch Android Gradle files:', err && err.message);
353
- }
354
-
355
- return config;
356
- },
357
- ]);
358
- };
359
-
360
- /**
361
- * Main plugin that combines iOS and Android configurations
362
- */
363
- const withFlirThermalSDK = (config, props = {}) => {
364
- // Apply iOS modifications
365
- config = withFlirInfoPlist(config, props);
366
- config = withFlirEntitlements(config);
367
- config = withFlirManifest(config);
368
-
369
- // Apply Android modifications
370
- config = withFlirAndroidManifest(config, props);
371
- config = withFlirAndroidAssets(config);
372
- config = withFlirAndroidGradle(config);
373
-
374
- return config;
375
- };
376
-
377
- module.exports = createRunOncePlugin(
378
- withFlirThermalSDK,
379
- 'ilabs-flir',
380
- '2.0.3'
381
- );
1
+ const {
2
+ withInfoPlist,
3
+ withAndroidManifest,
4
+ withDangerousMod,
5
+ withEntitlementsPlist,
6
+ createRunOncePlugin,
7
+ } = require('@expo/config-plugins');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ /**
12
+ * FLIR Thermal SDK Config Plugin
13
+ *
14
+ * Automatically adds required iOS Info.plist and Android AndroidManifest.xml
15
+ * entries for FLIR device support. This eliminates the need for manual editing.
16
+ *
17
+ * Usage in app.json:
18
+ * {
19
+ * "plugins": ["ilabs-flir"]
20
+ * }
21
+ *
22
+ * Or with options:
23
+ * {
24
+ * "plugins": [
25
+ * ["ilabs-flir", {
26
+ * "lightningOnly": true, // iOS ONLY: Skip network/WiFi permissions
27
+ * "disableNetworkPermissions": true, // Same as lightningOnly (iOS only)
28
+ * "bluetoothAlwaysUsageDescription": "Custom description",
29
+ * "localNetworkUsageDescription": "Custom description"
30
+ * }]
31
+ * ]
32
+ * }
33
+ *
34
+ * FLAGS:
35
+ * - lightningOnly / disableNetworkPermissions (iOS ONLY):
36
+ * Set to true to skip Local Network, Bonjour, and Bluetooth permissions on iOS.
37
+ * Use this if you only need Lightning-connected FLIR ONE devices
38
+ * and don't have a paid Apple Developer license.
39
+ * NOTE: This flag does NOT affect Android - Android always gets all permissions.
40
+ */
41
+
42
+ // External Accessory Protocols for FLIR ONE Lightning devices
43
+ const EXTERNAL_ACCESSORY_PROTOCOLS = [
44
+ 'com.flir.rosebud.config',
45
+ 'com.flir.rosebud.frame',
46
+ 'com.flir.rosebud.fileio',
47
+ ];
48
+
49
+ // Bonjour services for FLIR network discovery
50
+ const BONJOUR_SERVICES = [
51
+ '_flir._tcp',
52
+ '_http._tcp',
53
+ ];
54
+
55
+ // Default permission descriptions
56
+ const DEFAULT_DESCRIPTIONS = {
57
+ bluetoothAlways: 'This app requires Bluetooth to connect to FLIR ONE Edge and other wireless thermal cameras via Bluetooth Low Energy.',
58
+ bluetoothPeripheral: 'This app uses Bluetooth to communicate with FLIR thermal imaging devices.',
59
+ localNetwork: 'This app needs local network access to discover and connect to FLIR thermal cameras on your WiFi network.',
60
+ camera: 'This app uses the camera to capture photos and video alongside thermal imaging.',
61
+ accessoryConnection: 'This app connects to FLIR ONE thermal camera accessories.',
62
+ };
63
+
64
+ /**
65
+ * Adds FLIR-specific Info.plist entries for iOS
66
+ */
67
+ const withFlirInfoPlist = (config, props = {}) => {
68
+ return withInfoPlist(config, (config) => {
69
+ const infoPlist = config.modResults;
70
+
71
+ // Check if network permissions should be skipped
72
+ const skipNetworkPermissions = props.lightningOnly === true ||
73
+ props.disableNetworkPermissions === true;
74
+
75
+ if (skipNetworkPermissions) {
76
+ console.log('[ilabs-flir] ⚠️ lightningOnly mode: Skipping network/WiFi permissions');
77
+ }
78
+
79
+ // =========================================================================
80
+ // EXTERNAL ACCESSORY PROTOCOLS
81
+ // Required for FLIR ONE devices connected via Lightning port
82
+ // =========================================================================
83
+ if (!infoPlist.UISupportedExternalAccessoryProtocols) {
84
+ infoPlist.UISupportedExternalAccessoryProtocols = [];
85
+ }
86
+
87
+ const existingProtocols = infoPlist.UISupportedExternalAccessoryProtocols;
88
+ EXTERNAL_ACCESSORY_PROTOCOLS.forEach((protocol) => {
89
+ if (!existingProtocols.includes(protocol)) {
90
+ existingProtocols.push(protocol);
91
+ }
92
+ });
93
+
94
+ // =========================================================================
95
+ // BLUETOOTH PERMISSIONS
96
+ // Required for FLIR ONE Edge, FLIR ONE Pro, and other BLE thermal cameras
97
+ // Skip if lightningOnly mode (BLE requires network discovery in some cases)
98
+ // =========================================================================
99
+
100
+ if (!skipNetworkPermissions) {
101
+ // iOS 13+ requires NSBluetoothAlwaysUsageDescription
102
+ if (!infoPlist.NSBluetoothAlwaysUsageDescription) {
103
+ infoPlist.NSBluetoothAlwaysUsageDescription =
104
+ props.bluetoothAlwaysUsageDescription || DEFAULT_DESCRIPTIONS.bluetoothAlways;
105
+ }
106
+
107
+ // Older iOS versions (pre-13) require NSBluetoothPeripheralUsageDescription
108
+ if (!infoPlist.NSBluetoothPeripheralUsageDescription) {
109
+ infoPlist.NSBluetoothPeripheralUsageDescription =
110
+ props.bluetoothPeripheralUsageDescription || DEFAULT_DESCRIPTIONS.bluetoothPeripheral;
111
+ }
112
+ }
113
+
114
+ // =========================================================================
115
+ // LOCAL NETWORK PERMISSION (iOS 14+)
116
+ // Required for discovering FLIR cameras over WiFi
117
+ // NOTE: This requires a PAID Apple Developer account to work properly
118
+ // SKIP if lightningOnly mode
119
+ // =========================================================================
120
+ if (!skipNetworkPermissions) {
121
+ if (!infoPlist.NSLocalNetworkUsageDescription) {
122
+ infoPlist.NSLocalNetworkUsageDescription =
123
+ props.localNetworkUsageDescription || DEFAULT_DESCRIPTIONS.localNetwork;
124
+ }
125
+
126
+ // =========================================================================
127
+ // BONJOUR SERVICES
128
+ // Required for mDNS/Bonjour network discovery of FLIR cameras
129
+ // SKIP if lightningOnly mode
130
+ // =========================================================================
131
+ if (!infoPlist.NSBonjourServices) {
132
+ infoPlist.NSBonjourServices = [];
133
+ }
134
+
135
+ const existingBonjour = infoPlist.NSBonjourServices;
136
+ BONJOUR_SERVICES.forEach((service) => {
137
+ if (!existingBonjour.includes(service)) {
138
+ existingBonjour.push(service);
139
+ }
140
+ });
141
+ }
142
+
143
+ // =========================================================================
144
+ // CAMERA PERMISSION (always added)
145
+ // Required if using visual camera alongside thermal
146
+ // =========================================================================
147
+ if (!infoPlist.NSCameraUsageDescription) {
148
+ infoPlist.NSCameraUsageDescription =
149
+ props.cameraUsageDescription || DEFAULT_DESCRIPTIONS.camera;
150
+ }
151
+
152
+ // =========================================================================
153
+ // ACCESSORY CONNECTION (iOS 16.4+)
154
+ // Only needed for wireless accessories - skip in lightningOnly mode
155
+ // =========================================================================
156
+ if (!skipNetworkPermissions) {
157
+ if (!infoPlist.NSAccessorySetupUsageDescription) {
158
+ infoPlist.NSAccessorySetupUsageDescription =
159
+ props.accessorySetupUsageDescription || DEFAULT_DESCRIPTIONS.accessoryConnection;
160
+ }
161
+ }
162
+
163
+ // =========================================================================
164
+ // BACKGROUND MODES (always added for Lightning accessory)
165
+ // Enable external accessory communication in background
166
+ // =========================================================================
167
+ if (!infoPlist.UIBackgroundModes) {
168
+ infoPlist.UIBackgroundModes = [];
169
+ }
170
+
171
+ const backgroundModes = infoPlist.UIBackgroundModes;
172
+ if (!backgroundModes.includes('external-accessory')) {
173
+ backgroundModes.push('external-accessory');
174
+ }
175
+
176
+ return config;
177
+ });
178
+ };
179
+
180
+ /**
181
+ * Adds FLIR-specific entitlements for iOS
182
+ */
183
+ const withFlirEntitlements = (config) => {
184
+ return withEntitlementsPlist(config, (config) => {
185
+ // Currently no special entitlements needed
186
+ return config;
187
+ });
188
+ };
189
+
190
+ /**
191
+ * Adds FLIR-specific AndroidManifest.xml entries for Android
192
+ * NOTE: The lightningOnly flag does NOT apply to Android.
193
+ * Android always gets all permissions since it doesn't have the same
194
+ * Apple Developer license restrictions for network discovery.
195
+ */
196
+ const withFlirAndroidManifest = (config, props = {}) => {
197
+ return withAndroidManifest(config, (config) => {
198
+ const androidManifest = config.modResults;
199
+ const mainApplication = androidManifest.manifest;
200
+
201
+ // Ensure uses-feature array exists
202
+ if (!mainApplication['uses-feature']) {
203
+ mainApplication['uses-feature'] = [];
204
+ }
205
+
206
+ // Ensure uses-permission array exists
207
+ if (!mainApplication['uses-permission']) {
208
+ mainApplication['uses-permission'] = [];
209
+ }
210
+
211
+ // Helper to add feature
212
+ const addFeature = (name, required = false) => {
213
+ const hasFeature = mainApplication['uses-feature'].some(
214
+ (f) => f.$?.['android:name'] === name
215
+ );
216
+ if (!hasFeature) {
217
+ mainApplication['uses-feature'].push({
218
+ $: {
219
+ 'android:name': name,
220
+ 'android:required': required ? 'true' : 'false',
221
+ },
222
+ });
223
+ }
224
+ };
225
+
226
+ // Helper to add permission
227
+ const addPermission = (name) => {
228
+ const hasPermission = mainApplication['uses-permission'].some(
229
+ (p) => p.$?.['android:name'] === name
230
+ );
231
+ if (!hasPermission) {
232
+ mainApplication['uses-permission'].push({
233
+ $: { 'android:name': name },
234
+ });
235
+ }
236
+ };
237
+
238
+ // USB host feature for FLIR ONE USB devices
239
+ addFeature('android.hardware.usb.host', false);
240
+
241
+ // Camera permission
242
+ addPermission('android.permission.CAMERA');
243
+
244
+ // Bluetooth features for FLIR ONE Edge/Pro
245
+ addFeature('android.hardware.bluetooth', false);
246
+ addFeature('android.hardware.bluetooth_le', false);
247
+
248
+ // WiFi feature for network cameras
249
+ addFeature('android.hardware.wifi', false);
250
+
251
+ // Network permissions (always added on Android)
252
+ addPermission('android.permission.INTERNET');
253
+ addPermission('android.permission.ACCESS_NETWORK_STATE');
254
+ addPermission('android.permission.ACCESS_WIFI_STATE');
255
+ addPermission('android.permission.BLUETOOTH');
256
+ addPermission('android.permission.BLUETOOTH_ADMIN');
257
+ addPermission('android.permission.BLUETOOTH_CONNECT');
258
+ addPermission('android.permission.BLUETOOTH_SCAN');
259
+ addPermission('android.permission.ACCESS_FINE_LOCATION'); // Required for BLE scanning
260
+
261
+ return config;
262
+ });
263
+ };
264
+
265
+ /**
266
+ * Copies sdk-manifest.json to iOS project
267
+ */
268
+ const withFlirManifest = (config) => {
269
+ return withDangerousMod(config, [
270
+ 'ios',
271
+ async (config) => {
272
+ const src = path.join(__dirname, 'sdk-manifest.json');
273
+ const dst = path.join(config.modRequest.platformProjectRoot, 'sdk-manifest.json');
274
+ if (fs.existsSync(src)) {
275
+ fs.copyFileSync(src, dst);
276
+ }
277
+ return config;
278
+ },
279
+ ]);
280
+ };
281
+
282
+ /**
283
+ * Copies sdk-manifest.json to Android assets
284
+ */
285
+ const withFlirAndroidAssets = (config) => {
286
+ return withDangerousMod(config, [
287
+ 'android',
288
+ async (config) => {
289
+ const src = path.join(__dirname, 'sdk-manifest.json');
290
+ const dst = path.join(config.modRequest.platformProjectRoot, 'app/src/main/assets/sdk-manifest.json');
291
+ if (fs.existsSync(src)) {
292
+ fs.mkdirSync(path.dirname(dst), { recursive: true });
293
+ fs.copyFileSync(src, dst);
294
+ }
295
+ return config;
296
+ },
297
+ ]);
298
+ };
299
+
300
+ /**
301
+ * Ensure the Flir Android Gradle module is included in generated projects.
302
+ */
303
+ const withFlirAndroidGradle = (config) => {
304
+ return withDangerousMod(config, [
305
+ 'android',
306
+ async (config) => {
307
+ try {
308
+ const projectRoot = config.modRequest.platformProjectRoot;
309
+ const settingsGradlePath = path.join(projectRoot, 'settings.gradle');
310
+ const appBuildGradlePath = path.join(projectRoot, 'app', 'build.gradle');
311
+
312
+ const moduleRelPath = '../node_modules/ilabs-flir/android/Flir';
313
+ const includeSnippet = `\n// ilabs-flir: include Flir module\nif (new File(rootProject.projectDir, '${moduleRelPath}').exists()) {\n include ':Flir'\n project(':Flir').projectDir = new File(rootProject.projectDir, '${moduleRelPath}')\n}\n`;
314
+
315
+ if (fs.existsSync(settingsGradlePath)) {
316
+ let settingsTxt = fs.readFileSync(settingsGradlePath, 'utf8');
317
+ if (!/include\s*':Flir'/.test(settingsTxt)) {
318
+ fs.appendFileSync(settingsGradlePath, includeSnippet, 'utf8');
319
+ }
320
+ }
321
+
322
+ if (fs.existsSync(appBuildGradlePath)) {
323
+ let buildTxt = fs.readFileSync(appBuildGradlePath, 'utf8');
324
+ if (!/project\('\:Flir'\)/.test(buildTxt)) {
325
+ const depSnippet = `\n // ilabs-flir: include :Flir when available\n if (new File(rootDir.getParent(), '${moduleRelPath}').exists()) {\n implementation project(':Flir')\n }\n`;
326
+
327
+ const depIndex = buildTxt.search(/\bdependencies\s*\{/);
328
+ if (depIndex !== -1) {
329
+ let depth = 0;
330
+ let insertPos = -1;
331
+ for (let i = depIndex; i < buildTxt.length; i++) {
332
+ const ch = buildTxt[i];
333
+ if (ch === '{') depth++;
334
+ else if (ch === '}') {
335
+ depth--;
336
+ if (depth === 0) { insertPos = i; break; }
337
+ }
338
+ }
339
+
340
+ if (insertPos !== -1) {
341
+ buildTxt = buildTxt.slice(0, insertPos) + depSnippet + buildTxt.slice(insertPos);
342
+ fs.writeFileSync(appBuildGradlePath, buildTxt, 'utf8');
343
+ } else {
344
+ fs.appendFileSync(appBuildGradlePath, '\n' + depSnippet, 'utf8');
345
+ }
346
+ } else {
347
+ fs.appendFileSync(appBuildGradlePath, '\n' + 'dependencies {' + depSnippet + '\n}\n', 'utf8');
348
+ }
349
+ }
350
+ }
351
+ } catch (err) {
352
+ console.warn('[flir-config-plugin] Failed to patch Android Gradle files:', err && err.message);
353
+ }
354
+
355
+ return config;
356
+ },
357
+ ]);
358
+ };
359
+
360
+ /**
361
+ * Main plugin that combines iOS and Android configurations
362
+ */
363
+ const withFlirThermalSDK = (config, props = {}) => {
364
+ // Apply iOS modifications
365
+ config = withFlirInfoPlist(config, props);
366
+ config = withFlirEntitlements(config);
367
+ config = withFlirManifest(config);
368
+
369
+ // Apply Android modifications
370
+ config = withFlirAndroidManifest(config, props);
371
+ config = withFlirAndroidAssets(config);
372
+ config = withFlirAndroidGradle(config);
373
+
374
+ return config;
375
+ };
376
+
377
+ module.exports = createRunOncePlugin(
378
+ withFlirThermalSDK,
379
+ 'ilabs-flir',
380
+ '2.0.3'
381
+ );