From ad83de97b013ff8c5324a38338aedd38a97ccf7d Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Fri, 16 Aug 2024 11:25:43 +0200 Subject: [PATCH] refactor: rewrite certificate check module to be more detailed --- .gitignore | 8 +-- .../check-ios-app-id/expo-module.config.json | 6 +++ apps/expo/modules/check-ios-app-id/index.ts | 9 ++++ .../ios/CheckIosAppId.podspec} | 6 +-- .../ios/CheckIosAppIdModule.swift | 54 +++++++++++++++++++ .../src/CheckIosAppIdModule.android.ts | 10 ++++ .../src/CheckIosAppIdModule.ts} | 7 ++- .../expo-module.config.json | 6 --- .../modules/check-ios-certificate/index.ts | 11 ---- .../ios/CheckIosCertificateModule.swift | 37 ------------- .../src/CheckIosCertificateModule.android.ts | 10 ---- .../modules/check-ios-marketplace/index.ts | 20 +------ .../src/CheckIosMarketplaceModule.ts | 19 ++++++- apps/expo/package.json | 2 +- apps/expo/src/app/(tabs)/downloads.tsx | 19 +++---- .../src/components/player/BottomControls.tsx | 14 +++-- 16 files changed, 127 insertions(+), 111 deletions(-) create mode 100644 apps/expo/modules/check-ios-app-id/expo-module.config.json create mode 100644 apps/expo/modules/check-ios-app-id/index.ts rename apps/expo/modules/{check-ios-certificate/ios/CheckIosCertificate.podspec => check-ios-app-id/ios/CheckIosAppId.podspec} (71%) create mode 100644 apps/expo/modules/check-ios-app-id/ios/CheckIosAppIdModule.swift create mode 100644 apps/expo/modules/check-ios-app-id/src/CheckIosAppIdModule.android.ts rename apps/expo/modules/{check-ios-certificate/src/CheckIosCertificateModule.ts => check-ios-app-id/src/CheckIosAppIdModule.ts} (55%) delete mode 100644 apps/expo/modules/check-ios-certificate/expo-module.config.json delete mode 100644 apps/expo/modules/check-ios-certificate/index.ts delete mode 100644 apps/expo/modules/check-ios-certificate/ios/CheckIosCertificateModule.swift delete mode 100644 apps/expo/modules/check-ios-certificate/src/CheckIosCertificateModule.android.ts diff --git a/.gitignore b/.gitignore index 9e9a2e8..3b3865b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,10 +17,10 @@ coverage dist/ expo-env.d.ts apps/expo/.gitignore -ios/ -android/ -!modules/*/ios/ -!modules/*/android/ + +# Ignore top-level ios and android directories +apps/expo/ios/ +apps/expo/android/ # production build diff --git a/apps/expo/modules/check-ios-app-id/expo-module.config.json b/apps/expo/modules/check-ios-app-id/expo-module.config.json new file mode 100644 index 0000000..8717dc5 --- /dev/null +++ b/apps/expo/modules/check-ios-app-id/expo-module.config.json @@ -0,0 +1,6 @@ +{ + "platforms": ["ios"], + "ios": { + "modules": ["CheckIosAppIdModule"] + } +} diff --git a/apps/expo/modules/check-ios-app-id/index.ts b/apps/expo/modules/check-ios-app-id/index.ts new file mode 100644 index 0000000..666455b --- /dev/null +++ b/apps/expo/modules/check-ios-app-id/index.ts @@ -0,0 +1,9 @@ +import CheckIosAppIdModule from "./src/CheckIosAppIdModule"; + +export function isIncorrectAppId(): boolean { + return CheckIosAppIdModule.isIncorrectAppId(); +} + +export function getAppId(): string { + return CheckIosAppIdModule.getAppId(); +} diff --git a/apps/expo/modules/check-ios-certificate/ios/CheckIosCertificate.podspec b/apps/expo/modules/check-ios-app-id/ios/CheckIosAppId.podspec similarity index 71% rename from apps/expo/modules/check-ios-certificate/ios/CheckIosCertificate.podspec rename to apps/expo/modules/check-ios-app-id/ios/CheckIosAppId.podspec index 597e515..71d1c58 100644 --- a/apps/expo/modules/check-ios-certificate/ios/CheckIosCertificate.podspec +++ b/apps/expo/modules/check-ios-app-id/ios/CheckIosAppId.podspec @@ -1,8 +1,8 @@ Pod::Spec.new do |s| - s.name = 'CheckIosCertificate' + s.name = 'CheckIosAppId' s.version = '1.0.0' - s.summary = 'Check if iOS certificate is Development or Production.' - s.description = 'Check if iOS certificate is Development or Production.' + s.summary = 'Check if iOS App ID is explicit or wildcard.' + s.description = 'Check if iOS App ID is explicit or wildcard.' s.author = 'castdrian' s.homepage = 'https://docs.expo.dev/modules/' s.platforms = { :ios => '13.4', :tvos => '13.4' } diff --git a/apps/expo/modules/check-ios-app-id/ios/CheckIosAppIdModule.swift b/apps/expo/modules/check-ios-app-id/ios/CheckIosAppIdModule.swift new file mode 100644 index 0000000..e0ef1e9 --- /dev/null +++ b/apps/expo/modules/check-ios-app-id/ios/CheckIosAppIdModule.swift @@ -0,0 +1,54 @@ +import ExpoModulesCore + +public class CheckIosAppIdModule: Module { + public func definition() -> ModuleDefinition { + Name("CheckIosAppId") + + Function("isIncorrectAppId") { () -> Bool in + #if targetEnvironment(simulator) + return false + #else + guard let appId = self.extractAppId() else { + return false + } + + return appId.hasSuffix(".*") || (Bundle.main.bundleIdentifier != nil && !appId.contains(Bundle.main.bundleIdentifier!)) + #endif + } + + // Function to get the App ID from the provisioning profile + Function("getAppId") { () -> String? in + #if targetEnvironment(simulator) + return nil + #else + return self.extractAppId() + #endif + } + } + + // Helper function to extract the application-identifier value from the provisioning profile + private func extractAppId() -> String? { + guard let filePath = Bundle.main.path(forResource: "embedded", ofType: "mobileprovision") else { + return nil + } + + let fileURL = URL(fileURLWithPath: filePath) + do { + let data = try String(contentsOf: fileURL, encoding: .ascii) + let cleared = data.components(separatedBy: .whitespacesAndNewlines).joined() + + // Search for the application-identifier key and extract its value + if let range = cleared.range(of: "application-identifier") { + let substring = cleared[range.upperBound...] + if let endRange = substring.range(of: "") { + let appId = String(substring[.. ModuleDefinition { - // Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument. - // Can be inferred from module's class name, but it's recommended to set it explicitly for clarity. - // The module will be accessible from `requireNativeModule('CheckIosCertificate')` in JavaScript. - Name("CheckIosCertificate") - - // Defines a JavaScript synchronous function that runs the native code on the JavaScript thread. - Function("isDevelopmentProvisioningProfile") { () -> Any in - #if targetEnvironment(simulator) - // Running on the Simulator - return true - #else - // Check for provisioning profile for non-Simulator execution - guard let filePath = Bundle.main.path(forResource: "embedded", ofType: "mobileprovision") else { - return false - } - - let fileURL = URL(fileURLWithPath: filePath) - do { - let data = try String(contentsOf: fileURL, encoding: .ascii) - let cleared = data.components(separatedBy: .whitespacesAndNewlines).joined() - return cleared.contains("get-task-allow") - } catch { - // Handling error if the file read fails - print("Error reading provisioning profile: \(error)") - return false - } - #endif - } - } -} diff --git a/apps/expo/modules/check-ios-certificate/src/CheckIosCertificateModule.android.ts b/apps/expo/modules/check-ios-certificate/src/CheckIosCertificateModule.android.ts deleted file mode 100644 index fa041d2..0000000 --- a/apps/expo/modules/check-ios-certificate/src/CheckIosCertificateModule.android.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { UnavailabilityError } from "expo-modules-core"; - -export default { - isDevelopmentProvisioningProfile: () => { - throw new UnavailabilityError( - "CheckIosCertificate", - "isDevelopmentProvisioningProfile", - ); - }, -}; diff --git a/apps/expo/modules/check-ios-marketplace/index.ts b/apps/expo/modules/check-ios-marketplace/index.ts index 3b5f8c6..c6dd3e1 100644 --- a/apps/expo/modules/check-ios-marketplace/index.ts +++ b/apps/expo/modules/check-ios-marketplace/index.ts @@ -1,22 +1,6 @@ +import type { MarketplaceSource } from "./src/CheckIosMarketplaceModule"; import CheckIosMarketplaceModule from "./src/CheckIosMarketplaceModule"; -export enum MarketplaceSource { - AppStore = "App Store", - TestFlight = "TestFlight", - Marketplace = "Alternative marketplace", - Web = "Website", - Other = "Other", - Unknown = "Unknown", - Error = "Error", - Unavailable = "Unavailable", -} - -interface CheckIosMarketplaceModule { - getCurrentMarketplaceAsync(): Promise; -} - export async function getCurrentMarketplaceAsync(): Promise { - return ( - CheckIosMarketplaceModule as CheckIosMarketplaceModule - ).getCurrentMarketplaceAsync(); + return CheckIosMarketplaceModule.getCurrentMarketplaceAsync(); } diff --git a/apps/expo/modules/check-ios-marketplace/src/CheckIosMarketplaceModule.ts b/apps/expo/modules/check-ios-marketplace/src/CheckIosMarketplaceModule.ts index 5d729a3..a025047 100644 --- a/apps/expo/modules/check-ios-marketplace/src/CheckIosMarketplaceModule.ts +++ b/apps/expo/modules/check-ios-marketplace/src/CheckIosMarketplaceModule.ts @@ -1,5 +1,22 @@ import { requireNativeModule } from "expo-modules-core"; +export enum MarketplaceSource { + AppStore = "App Store", + TestFlight = "TestFlight", + Marketplace = "Alternative marketplace", + Web = "Website", + Other = "Other", + Unknown = "Unknown", + Error = "Error", + Unavailable = "Unavailable", +} + +interface CheckIosMarketplaceModule { + getCurrentMarketplaceAsync(): Promise; +} + // It loads the native module object from the JSI or falls back to // the bridge module (from NativeModulesProxy) if the remote debugger is on. -export default requireNativeModule("CheckIosMarketplace"); +export default requireNativeModule( + "CheckIosMarketplace", +) as CheckIosMarketplaceModule; diff --git a/apps/expo/package.json b/apps/expo/package.json index 3f86cfc..bf589dd 100644 --- a/apps/expo/package.json +++ b/apps/expo/package.json @@ -11,7 +11,7 @@ "android": "expo run:android", "ios": "expo run:ios", "apk": "expo prebuild --platform=android && cd android && ./gradlew assembleRelease && mv app/build/outputs/apk/release/app-release.apk app/build/movie-web.apk", - "ipa": "expo prebuild --platform=ios && cd ios && xcodebuild clean archive -workspace movieweb.xcworkspace -scheme movieweb -configuration Release -destination generic/platform=iOS -archivePath build/movieweb.xcarchive CODE_SIGN_IDENTITY=\"\" CODE_SIGNING_ALLOWED=NO | xcbeautify && cd build/movieweb.xcarchive/Products && mv Applications Payload && zip -r movie-web.ipa Payload && mv movie-web.ipa ../..", + "ipa": "expo prebuild --platform=ios && cd ios && xcodebuild archive -workspace movieweb.xcworkspace -scheme movieweb -configuration Release -destination generic/platform=iOS -archivePath build/movieweb.xcarchive CODE_SIGN_IDENTITY=\"\" CODE_SIGNING_ALLOWED=NO | xcbeautify && cd build/movieweb.xcarchive/Products && mv Applications Payload && zip -r movie-web.ipa Payload && mv movie-web.ipa ../..", "ipa:sim": "expo prebuild --platform=ios && cd ios && xcodebuild clean archive -workspace movieweb.xcworkspace -scheme movieweb -configuration Release -destination \"generic/platform=iOS Simulator\" -archivePath build/movieweb.xcarchive CODE_SIGN_IDENTITY=\"\" CODE_SIGNING_ALLOWED=NO | xcbeautify && cd build/movieweb.xcarchive/Products && mv Applications Payload && zip -r movie-web.ipa Payload && mv movie-web.ipa ../..", "format": "prettier --check . --ignore-path ../../.gitignore", "lint": "eslint .", diff --git a/apps/expo/src/app/(tabs)/downloads.tsx b/apps/expo/src/app/(tabs)/downloads.tsx index cc0997b..4be65bc 100644 --- a/apps/expo/src/app/(tabs)/downloads.tsx +++ b/apps/expo/src/app/(tabs)/downloads.tsx @@ -2,7 +2,7 @@ import React from "react"; import { Alert, Platform } from "react-native"; import { useFocusEffect, useRouter } from "expo-router"; import { MaterialCommunityIcons } from "@expo/vector-icons"; -import { isDevelopmentProvisioningProfile } from "modules/check-ios-certificate"; +import { getAppId, isIncorrectAppId } from "modules/check-ios-app-id"; import { ScrollView, useTheme, YStack } from "tamagui"; import type { ScrapeMedia } from "@movie-web/provider-utils"; @@ -81,10 +81,11 @@ const DownloadsScreen: React.FC = () => { useFocusEffect( React.useCallback(() => { - if (Platform.OS === "ios" && !isDevelopmentProvisioningProfile()) { + if (Platform.OS === "ios" && isIncorrectAppId()) { + const appId = getAppId(); Alert.alert( - "Production Certificate", - "Download functionality is not available when the application is signed with a distribution certificate.", + "Wildcard/Mismatching App ID", + `The application is signed with a wildcard or mismatching App ID (${appId}). Download functionality is not available when the application is signed with a wildcard or mismatching App ID.`, [ { text: "OK", @@ -148,14 +149,10 @@ const DownloadsScreen: React.FC = () => { onPress={() => handlePress(download.downloads[0]!.localPath)} /> ); - } else { - return ( - - ); } + return ( + + ); })} diff --git a/apps/expo/src/components/player/BottomControls.tsx b/apps/expo/src/components/player/BottomControls.tsx index 76c8562..d7dc574 100644 --- a/apps/expo/src/components/player/BottomControls.tsx +++ b/apps/expo/src/components/player/BottomControls.tsx @@ -5,7 +5,7 @@ import Animated, { useSharedValue, withTiming, } from "react-native-reanimated"; -import { isDevelopmentProvisioningProfile } from "modules/check-ios-certificate"; +import { isIncorrectAppId } from "modules/check-ios-app-id"; import { Text, View } from "tamagui"; import { LinearGradient } from "tamagui/linear-gradient"; @@ -39,12 +39,11 @@ export const BottomControls = () => { (player.duration ?? 0) - (player.currentTime ?? 0), )}`; return { currentTime: current, remainingTime: remaining }; - } else { - return { - currentTime: mapSecondsToTime(0), - remainingTime: mapSecondsToTime(0), - }; } + return { + currentTime: mapSecondsToTime(0), + remainingTime: mapSecondsToTime(0), + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [player?.currentTime]); @@ -110,8 +109,7 @@ export const BottomControls = () => { {Platform.OS === "android" || - (Platform.OS === "ios" && - isDevelopmentProvisioningProfile()) ? ( + (Platform.OS === "ios" && !isIncorrectAppId()) ? ( ) : null}