fix(ios): add required location purpose string (#223)

## What changed
- add `NSLocationAlwaysAndWhenInUseUsageDescription` to the iOS app
- localize the purpose string in Danish and English
- enforce the key in mobile permission validation and App Store
readiness tests

## Why
App Store Connect accepted builds 1 and 2 but emitted `ITMS-90683`,
stating that the final app references APIs requiring this purpose
string. Shipping a corrected binary avoids submitting a candidate with a
known Apple delivery warning.

## Validation
- `node scripts/mobile/check-permissions.mjs`
- `node scripts/mobile/validate-app-store.mjs --strict`
- `vitest run tests/unit/app-store-product-readiness.spec.js` (5 tests)
- Prettier check for changed JS files
- `git diff --check`
This commit is contained in:
Jeppe B
2026-07-23 16:36:35 +00:00
committed by GitHub
parent bf2208e77b
commit 32418b42a0
5 changed files with 7 additions and 0 deletions
+2
View File
@@ -28,6 +28,8 @@
<false/>
<key>NSCameraUsageDescription</key>
<string>Truck Wash uses the camera to scan QR codes and vehicle registration plates.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Truck Wash uses your location to find or confirm the nearest truck wash department when you choose a location feature; the app does not track your location in the background.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Truck Wash uses your location while the app is open to find or confirm the nearest truck wash department.</string>
<key>UILaunchStoryboardName</key>
+1
View File
@@ -1,3 +1,4 @@
"CFBundleDisplayName" = "Truck Wash";
"NSCameraUsageDescription" = "Truck Wash bruger kameraet til at scanne QR-koder og registreringsnumre, når du vælger en scanningsfunktion.";
"NSLocationAlwaysAndWhenInUseUsageDescription" = "Truck Wash bruger din placering til at finde eller bekræfte den nærmeste Truck Wash-afdeling, når du vælger en placeringsfunktion; appen sporer ikke din placering i baggrunden.";
"NSLocationWhenInUseUsageDescription" = "Truck Wash bruger din placering, mens appen er åben, til at finde eller bekræfte den nærmeste Truck Wash-afdeling.";
+1
View File
@@ -1,3 +1,4 @@
"CFBundleDisplayName" = "Truck Wash";
"NSCameraUsageDescription" = "Truck Wash uses the camera to scan QR codes and vehicle registration plates when you choose a scanning feature.";
"NSLocationAlwaysAndWhenInUseUsageDescription" = "Truck Wash uses your location to find or confirm the nearest Truck Wash department when you choose a location feature; the app does not track your location in the background.";
"NSLocationWhenInUseUsageDescription" = "Truck Wash uses your location while the app is open to find or confirm the nearest Truck Wash department.";
+1
View File
@@ -28,6 +28,7 @@ requireText("AndroidManifest.xml", androidManifest, 'android:required="false"');
requireText("android/app/build.gradle", androidBuild, 'applicationId "io.truckwash.twa"');
requireText("android/app/build.gradle", androidBuild, "ANDROID_KEYSTORE_FILE");
requireText("Info.plist", iosInfoPlist, "NSCameraUsageDescription");
requireText("Info.plist", iosInfoPlist, "NSLocationAlwaysAndWhenInUseUsageDescription");
requireText("Info.plist", iosInfoPlist, "NSLocationWhenInUseUsageDescription");
requireText("project.pbxproj", iosProject, "PRODUCT_BUNDLE_IDENTIFIER = io.truckwash.app;");
requireText("project.pbxproj", iosProject, "PrivacyInfo.xcprivacy in Resources");
@@ -42,8 +42,10 @@ describe("App Store product readiness", () => {
expect(infoPlist).toContain("<key>ITSAppUsesNonExemptEncryption</key>");
expect(infoPlist).toMatch(/<key>ITSAppUsesNonExemptEncryption<\/key>\s*<false\/>/u);
expect(danishStrings).toContain('"NSCameraUsageDescription"');
expect(danishStrings).toContain('"NSLocationAlwaysAndWhenInUseUsageDescription"');
expect(danishStrings).toContain('"NSLocationWhenInUseUsageDescription"');
expect(englishStrings).toContain('"NSCameraUsageDescription"');
expect(englishStrings).toContain('"NSLocationAlwaysAndWhenInUseUsageDescription"');
expect(englishStrings).toContain('"NSLocationWhenInUseUsageDescription"');
});