Repository navigation
Expand file tree
/
Copy pathapp.config.ts
More file actions
336 lines (333 loc) · 14.9 KB
/
Copy pathapp.config.ts
File metadata and controls
336 lines (333 loc) · 14.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
/* eslint-disable max-lines-per-function */
import type { ConfigContext, ExpoConfig } from '@expo/config';
import type { AppIconBadgeConfig } from 'app-icon-badge/types';
import { ClientEnv, Env } from './env';
const packageJSON = require('./package.json');
const appIconBadgeConfig: AppIconBadgeConfig = {
enabled: Env.APP_ENV !== 'production',
badges: [
{
text: Env.APP_ENV,
type: 'banner',
color: 'white',
},
{
text: Env.VERSION.toString(),
type: 'ribbon',
color: 'white',
},
],
};
export default ({ config }: ConfigContext): ExpoConfig => ({
...config,
name: Env.NAME,
description: `${Env.NAME} Incident Command`,
owner: Env.EXPO_ACCOUNT_OWNER,
scheme: Env.SCHEME,
slug: 'resgrid-ic',
version: packageJSON.version,
orientation: 'default',
icon: './assets/icon.png',
userInterfaceStyle: 'automatic',
updates: {
fallbackToCacheTimeout: 0,
},
assetBundlePatterns: ['**/*'],
ios: {
icon: './assets/ios-icon.png',
version: packageJSON.version,
buildNumber: packageJSON.version,
supportsTablet: true,
bundleIdentifier: Env.BUNDLE_ID,
requireFullScreen: true,
// Passkeys: IC's own relying-party hosts, one per region (passkey workbook section 5.1; the IC web host is proposed).
// Each host serves an apple-app-site-association listing only this app. Staging and development builds use their own
// hosts, not these.
...((Env.APP_ENV === 'production' || Env.APP_ENV === 'internal') && {
associatedDomains: ['webcredentials:ic.resgrid.com', 'webcredentials:ic-eu-central.resgrid.com'],
}),
infoPlist: {
UIBackgroundModes: ['remote-notification', 'audio', 'bluetooth-central', 'voip'],
ITSAppUsesNonExemptEncryption: false,
UIViewControllerBasedStatusBarAppearance: false,
NSBluetoothAlwaysUsageDescription:
'Resgrid IC uses Bluetooth to connect to wireless headsets and speaker-microphone accessories for Push-to-Talk audio. For example, when you pair a Bluetooth speaker-mic, pressing its talk button transmits your voice to your department audio channel.',
// Allow the app to open its own custom-scheme deep links (needed for SSO callbacks).
// The navigation apps the Navigation App setting can hand directions to are detected with
// canOpenURL, which only answers for declared schemes (src/lib/navigation.ts).
LSApplicationQueriesSchemes: ['resgridic', 'waze', 'comgooglemaps'],
},
entitlements: {
// Required for APNs registration. Previously added by the withForegroundNotifications
// plugin; set explicitly so removing/swapping plugins can never silently drop it
// (which would break ALL iOS push).
'aps-environment': 'production',
...((Env.APP_ENV === 'production' || Env.APP_ENV === 'internal') && {
'com.apple.developer.usernotifications.critical-alerts': true,
'com.apple.developer.usernotifications.time-sensitive': true,
}),
},
},
experiments: {
typedRoutes: true,
},
android: {
version: packageJSON.version,
versionCode: parseInt(packageJSON.versionCode),
adaptiveIcon: {
foregroundImage: './assets/adaptive-icon.png',
backgroundColor: '#2484c4',
},
// 'pan' makes Android scroll the window under the IME on its own, which fights
// react-native-keyboard-controller. Its hooks flip the activity to adjustResize on
// mount and call setDefaultMode() on unmount, restoring whatever this value is — so
// with 'pan' any closing sheet or modal drops the app back into pan mode and inputs
// end up under the keyboard. Edge-to-edge means the OS no longer resizes for us
// either, so 'resize' leaves keyboard avoidance entirely to the library.
softwareKeyboardLayoutMode: 'resize',
package: Env.PACKAGE,
googleServicesFile: 'google-services.json',
// Register the ResgridIC:// deep-link scheme so OIDC / SAML callbacks are routed back here
intentFilters: [
{
action: 'VIEW',
autoVerify: false,
data: [{ scheme: 'resgridic' }],
category: ['BROWSABLE', 'DEFAULT'],
},
],
permissions: [
'android.permission.WAKE_LOCK',
'android.permission.RECORD_AUDIO',
'android.permission.CAPTURE_AUDIO_OUTPUT',
'android.permission.POST_NOTIFICATIONS',
'android.permission.FOREGROUND_SERVICE',
'android.permission.FOREGROUND_SERVICE_MICROPHONE',
'android.permission.FOREGROUND_SERVICE_PHONE_CALL',
// Department audio (scanner) streams keep playing while backgrounded through
// expo-audio's AudioControlsService. The expo-audio config plugin adds this permission
// too, but it is declared here so the FGS types the app actually uses are all visible
// in one place next to the Play declarations.
'android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK',
'android.permission.READ_PHONE_STATE',
'android.permission.READ_PHONE_NUMBERS',
'android.permission.MANAGE_OWN_CALLS',
],
// Media is selected through Android's system Photo Picker. Block broad media
// and legacy storage permissions even when a transitive native dependency
// contributes them during manifest merging.
blockedPermissions: [
// Bluetooth PTT handsets route through the microphone FGS session, so the type is
// unused; Play rejects declared foreground-service types that cannot be demonstrated.
'android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE',
// Background location was removed from the app (Play policy: no declarable
// background-location feature). Block the permissions outright so a transitive
// native dependency cannot reintroduce them during manifest merging.
'android.permission.ACCESS_BACKGROUND_LOCATION',
'android.permission.FOREGROUND_SERVICE_LOCATION',
// Contributed by expo-notifications. withRestrictedBootReceivers strips every
// BOOT_COMPLETED intent-filter (Android 15 crashes apps that launch restricted
// foreground service types from boot), so nothing here listens for boot and the
// permission would only be dead weight the Play Console keeps flagging.
'android.permission.RECEIVE_BOOT_COMPLETED',
'android.permission.READ_MEDIA_IMAGES',
'android.permission.READ_MEDIA_VIDEO',
'android.permission.READ_MEDIA_VISUAL_USER_SELECTED',
'android.permission.READ_EXTERNAL_STORAGE',
'android.permission.WRITE_EXTERNAL_STORAGE',
],
},
web: {
favicon: './assets/favicon.png',
bundler: 'metro',
},
plugins: [
[
'expo-splash-screen',
{
backgroundColor: '#2a7dd5',
image: './assets/adaptive-icon.png',
imageWidth: 250,
},
],
[
'expo-font',
{
fonts: ['./assets/fonts/Inter.ttf'],
},
],
'expo-localization',
'expo-router',
['react-native-edge-to-edge'],
'expo-web-browser',
[
'expo-secure-store',
{
// Required even though biometric-gated storage is not used: expo-secure-store
// instantiates LAContext() unconditionally (SecureStoreModule.swift), so App Store
// static analysis flags a missing NSFaceIDUsageDescription with ITMS-90683.
faceIDPermission:
'Resgrid IC uses Face ID to unlock the securely stored credentials that keep you signed in to your department. For example, after your device locks, Face ID confirms it is you before the app restores your session.',
},
],
'expo-image',
'expo-sharing',
'expo-status-bar',
[
'@rnmapbox/maps',
{
// Keep in step with the `mapbox` field of the installed @rnmapbox/maps — the JS
// bindings are generated against a specific native SDK, and pinning an older one
// makes style props the bindings emit (symbolZOffset and friends) trap natively.
RNMapboxMapsVersion: '11.23.1',
},
],
[
'expo-location',
{
// Foreground-only (see src/services/location.ts): the app never requests background
// location and has no feature that needs it, so every background flag stays off.
// isIosBackgroundLocationEnabled would add `location` to UIBackgroundModes, which App
// Review rejects (Guideline 2.5.4) when no persistent-location feature exists.
//
// This is the only location prompt users (and App Review) ever see. Guideline
// 5.1.1(ii) requires it to name each use and give a concrete example.
locationWhenInUsePermission:
'Resgrid IC uses your location while the app is open to show where you are on the incident map, to center the map when you set a call location, and to record where you were when you check in, attach a photo to a call, or share your location in chat. For example, when you check in during an incident, your location is included so the incident commander can confirm where you checked in from.',
// The "Always" keys must exist anyway: expo-location's background requester references
// requestAlwaysAuthorization, and App Store static analysis rejects the binary with
// ITMS-90683 when they are absent. The app never shows these prompts, so they state the
// same foreground-only use.
locationAlwaysAndWhenInUsePermission:
'Resgrid IC uses your location only while the app is open and never tracks it in the background. It shows where you are on the incident map and records where you were when you check in or attach a photo to a call. For example, when you check in during an incident, your location is included so the incident commander can confirm where you checked in from.',
locationAlwaysPermission:
'Resgrid IC uses your location only while the app is open and never tracks it in the background. It shows where you are on the incident map and records where you were when you check in or attach a photo to a call. For example, when you check in during an incident, your location is included so the incident commander can confirm where you checked in from.',
// Required even though getMotionActivityAsync() is never called: expo-location links
// CoreMotion (MotionActivityPermissionRequester), and App Store static analysis rejects
// the binary with ITMS-90683 whenever the framework is referenced and the string is absent.
motionUsagePermission:
'Resgrid IC uses motion data only to improve the accuracy of your position on the incident map while the app is open. For example, while you walk an incident scene, motion data helps keep your position marker accurate. Motion data never leaves your device.',
},
],
[
'expo-screen-orientation',
{
initialOrientation: 'DEFAULT',
},
],
[
'expo-build-properties',
{
android: {
extraProguardRules: '-keep class expo.modules.location.** { *; }',
extraMavenRepos: ['../../node_modules/@notifee/react-native/android/libs'],
targetSdkVersion: 36,
},
ios: {
deploymentTarget: '18.1',
useFrameworks: 'static',
// Apps built with the iOS 27 SDK must adopt the UIKit scene life cycle or they are
// killed at launch. Remove once on SDK 58, whose template adopts it by default.
enableSceneSupport: true,
},
},
],
[
'expo-asset',
{
assets: [
'assets/mapping',
'assets/audio/ui/space_notification1.mp3',
'assets/audio/ui/space_notification2.mp3',
'assets/audio/ui/positive_interface_beep.mp3',
'assets/audio/ui/software_interface_start.mp3',
'assets/audio/ui/software_interface_back.mp3',
],
},
],
[
'expo-document-picker',
{
iCloudContainerEnvironment: 'Production',
},
],
[
// Listed explicitly so the plugin's vague "Allow $(PRODUCT_NAME) to access your X"
// defaults (auto-applied during prebuild) never reach Info.plist — App Store
// Guideline 5.1.1(ii) requires purpose strings with a concrete example.
'expo-image-picker',
{
photosPermission:
'Resgrid IC uses your photo library so you can attach existing photos to calls and chat messages. For example, you can select a saved photo of an incident scene and share it with dispatch and other responders on the call.',
cameraPermission:
'Resgrid IC uses the camera to take photos that you attach to calls. For example, you can photograph an incident scene and attach the image to the active call for other responders to see.',
},
],
[
'@sentry/react-native/expo',
{
organization: 'sentry',
project: 'ic',
url: 'https://sentry.resgrid.net/',
},
],
[
'expo-navigation-bar',
{
position: 'relative',
hidden: true,
behavior: 'inset-touch',
},
],
[
'expo-audio',
{
microphonePermission:
'Resgrid IC uses the microphone to capture your voice for Push-to-Talk and voice communications with your department. For example, when you press and hold the talk button on the command screen, your voice is transmitted live to other responders on the channel.',
},
],
'expo-video',
'react-native-ble-manager',
'@livekit/react-native-expo-plugin',
[
// Explicit string so the WebRTC plugin can never fill in its vague camera default.
// LiveKit sessions in this app are audio-only (camera is disabled on join), so the
// only camera feature is photographing scenes to attach to calls.
'@config-plugins/react-native-webrtc',
{
cameraPermission:
'Resgrid IC uses the camera to take photos that you attach to calls. For example, you can photograph an incident scene and attach the image to the active call for other responders to see.',
},
],
'./plugins/withWebRTCFrameworkFix.js',
'@config-plugins/react-native-callkeep',
[
// Owns the FCM/local notification icon + color meta-data on Android (generates the
// white-silhouette drawable from the source PNG). Manual meta-data edits are stripped
// by this plugin, so branding MUST flow through these props.
'expo-notifications',
{
icon: './assets/notification-icon.png',
color: '#2484c4',
},
],
'./customGradle.plugin.js',
'./customManifest.plugin.js',
// Must run after customManifest.plugin.js: both edit the merged application node.
'./plugins/withRestrictedBootReceivers.js',
// Strips expo-location's location-typed foreground service: this app tracks location
// only in the foreground, so nothing may ship a background-location surface.
'./plugins/withoutBackgroundLocation.js',
'./plugins/withNotificationSounds.js',
'./plugins/withMediaButtonModule.js',
'./plugins/withInCallAudioModule.js',
'./plugins/withResourceBundleDeploymentTarget.js',
['./plugins/with-app-icon-badge.js', appIconBadgeConfig],
],
extra: {
...ClientEnv,
eas: {
projectId: Env.EAS_PROJECT_ID,
},
},
});