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.
- package/Flir.podspec +139 -139
- package/README.md +1066 -1066
- package/android/Flir/build.gradle.kts +72 -72
- package/android/Flir/src/main/AndroidManifest.xml +45 -45
- package/android/Flir/src/main/java/flir/android/FlirCommands.java +136 -136
- package/android/Flir/src/main/java/flir/android/FlirFrameCache.kt +6 -6
- package/android/Flir/src/main/java/flir/android/FlirManager.kt +476 -476
- package/android/Flir/src/main/java/flir/android/FlirModule.kt +257 -257
- package/android/Flir/src/main/java/flir/android/FlirPackage.kt +18 -18
- package/android/Flir/src/main/java/flir/android/FlirSDKLoader.kt +74 -74
- package/android/Flir/src/main/java/flir/android/FlirSdkManager.java +583 -583
- package/android/Flir/src/main/java/flir/android/FlirStatus.kt +12 -12
- package/android/Flir/src/main/java/flir/android/FlirView.kt +48 -48
- package/android/Flir/src/main/java/flir/android/FlirViewManager.kt +13 -13
- package/app.plugin.js +381 -381
- package/expo-module.config.json +5 -5
- package/ios/Flir/src/Flir-Bridging-Header.h +34 -34
- package/ios/Flir/src/FlirEventEmitter.h +25 -25
- package/ios/Flir/src/FlirEventEmitter.m +63 -63
- package/ios/Flir/src/FlirManager.swift +599 -599
- package/ios/Flir/src/FlirModule.h +17 -17
- package/ios/Flir/src/FlirModule.m +713 -713
- package/ios/Flir/src/FlirPreviewView.h +13 -13
- package/ios/Flir/src/FlirPreviewView.m +171 -171
- package/ios/Flir/src/FlirState.h +68 -68
- package/ios/Flir/src/FlirState.m +135 -135
- package/ios/Flir/src/FlirViewManager.h +16 -16
- package/ios/Flir/src/FlirViewManager.m +27 -27
- package/package.json +72 -71
- package/react-native.config.js +14 -14
- package/scripts/fetch-binaries.js +47 -5
- package/sdk-manifest.json +50 -50
- package/src/index.d.ts +63 -63
- package/src/index.js +7 -7
- 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
|
+
);
|