From 7b1dd8170d709f16034b59eed2d746ccf081db4b Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Mon, 1 Apr 2024 23:44:49 +0200 Subject: [PATCH] feat: gate download behind development certificate on iOS --- .gitignore | 2 + .vscode/launch.json | 60 +++++++++++++++++++ .vscode/settings.json | 3 + .../expo-module.config.json | 6 ++ .../modules/check-ios-certificate/index.ts | 11 ++++ .../ios/CheckIosCertificate.podspec | 21 +++++++ .../ios/CheckIosCertificateModule.swift | 37 ++++++++++++ .../src/CheckIosCertificateModule.ts | 5 ++ apps/expo/src/app/(tabs)/downloads.tsx | 31 +++++++++- .../src/components/player/BottomControls.tsx | 8 ++- apps/expo/tsconfig.json | 1 + 11 files changed, 181 insertions(+), 4 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 apps/expo/modules/check-ios-certificate/expo-module.config.json create mode 100644 apps/expo/modules/check-ios-certificate/index.ts create mode 100644 apps/expo/modules/check-ios-certificate/ios/CheckIosCertificate.podspec create mode 100644 apps/expo/modules/check-ios-certificate/ios/CheckIosCertificateModule.swift create mode 100644 apps/expo/modules/check-ios-certificate/src/CheckIosCertificateModule.ts diff --git a/.gitignore b/.gitignore index 04e8dfa..9e9a2e8 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ expo-env.d.ts apps/expo/.gitignore ios/ android/ +!modules/*/ios/ +!modules/*/android/ # production build diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c4dfda6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,60 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Run iOS", + "request": "launch", + "runtimeArgs": [ + "ios", + ], + "cwd": "${workspaceFolder}/apps/expo", + "runtimeExecutable": "pnpm", + "skipFiles": [ + "/**" + ], + "type": "node" + }, + { + "name": "Run Android", + "request": "launch", + "runtimeArgs": [ + "android", + ], + "cwd": "${workspaceFolder}/apps/expo", + "runtimeExecutable": "pnpm", + "skipFiles": [ + "/**" + ], + "type": "node" + }, + { + "name": "Build IPA", + "request": "launch", + "runtimeArgs": [ + "ipa", + ], + "cwd": "${workspaceFolder}/apps/expo", + "runtimeExecutable": "pnpm", + "skipFiles": [ + "/**" + ], + "type": "node" + }, + { + "name": "Build APK", + "request": "launch", + "runtimeArgs": [ + "apk", + ], + "cwd": "${workspaceFolder}/apps/expo", + "runtimeExecutable": "pnpm", + "skipFiles": [ + "/**" + ], + "type": "node" + }, + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 87b90af..5a9385c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,5 +24,8 @@ ], "[github-actions-workflow]": { "editor.defaultFormatter": "esbenp.prettier-vscode" +}, +"[jsonc]": { + "editor.defaultFormatter": "vscode.json-language-features" } } diff --git a/apps/expo/modules/check-ios-certificate/expo-module.config.json b/apps/expo/modules/check-ios-certificate/expo-module.config.json new file mode 100644 index 0000000..1622170 --- /dev/null +++ b/apps/expo/modules/check-ios-certificate/expo-module.config.json @@ -0,0 +1,6 @@ +{ + "platforms": ["ios"], + "ios": { + "modules": ["CheckIosCertificateModule"] + } +} diff --git a/apps/expo/modules/check-ios-certificate/index.ts b/apps/expo/modules/check-ios-certificate/index.ts new file mode 100644 index 0000000..be2671d --- /dev/null +++ b/apps/expo/modules/check-ios-certificate/index.ts @@ -0,0 +1,11 @@ +// Import the native module. On web, it will be resolved to CheckIosCertificate.web.ts +// and on native platforms to CheckIosCertificate.ts +import CheckIosCertificateModule from "./src/CheckIosCertificateModule"; + +interface CheckIosCertificateModule { + isDevelopmentProvisioningProfile(): boolean; +} + +export function isDevelopmentProvisioningProfile(): boolean { + return (CheckIosCertificateModule as CheckIosCertificateModule).isDevelopmentProvisioningProfile(); +} diff --git a/apps/expo/modules/check-ios-certificate/ios/CheckIosCertificate.podspec b/apps/expo/modules/check-ios-certificate/ios/CheckIosCertificate.podspec new file mode 100644 index 0000000..5a962ab --- /dev/null +++ b/apps/expo/modules/check-ios-certificate/ios/CheckIosCertificate.podspec @@ -0,0 +1,21 @@ +Pod::Spec.new do |s| + s.name = 'CheckIosCertificate' + s.version = '1.0.0' + s.summary = 'A sample project summary' + s.description = 'A sample project description' + s.author = '' + s.homepage = 'https://docs.expo.dev/modules/' + s.platforms = { :ios => '13.4', :tvos => '13.4' } + s.source = { git: '' } + s.static_framework = true + + s.dependency 'ExpoModulesCore' + + # Swift/Objective-C compatibility + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + 'SWIFT_COMPILATION_MODE' => 'wholemodule' + } + + s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" +end diff --git a/apps/expo/modules/check-ios-certificate/ios/CheckIosCertificateModule.swift b/apps/expo/modules/check-ios-certificate/ios/CheckIosCertificateModule.swift new file mode 100644 index 0000000..ba3240a --- /dev/null +++ b/apps/expo/modules/check-ios-certificate/ios/CheckIosCertificateModule.swift @@ -0,0 +1,37 @@ +import ExpoModulesCore + +public class CheckIosCertificateModule: Module { + // Each module class must implement the definition function. The definition consists of components + // that describes the module's functionality and behavior. + // See https://docs.expo.dev/modules/module-api for more details about available components. + public func definition() -> 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") { + #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.ts b/apps/expo/modules/check-ios-certificate/src/CheckIosCertificateModule.ts new file mode 100644 index 0000000..4b82603 --- /dev/null +++ b/apps/expo/modules/check-ios-certificate/src/CheckIosCertificateModule.ts @@ -0,0 +1,5 @@ +import { requireNativeModule } from "expo-modules-core"; + +// 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("CheckIosCertificate"); diff --git a/apps/expo/src/app/(tabs)/downloads.tsx b/apps/expo/src/app/(tabs)/downloads.tsx index 02ac31f..01a00c9 100644 --- a/apps/expo/src/app/(tabs)/downloads.tsx +++ b/apps/expo/src/app/(tabs)/downloads.tsx @@ -1,18 +1,25 @@ import type { Asset } from "expo-media-library"; import React from "react"; +import { Alert, Platform } from "react-native"; import { ScrollView } from "react-native-gesture-handler"; -import { useRouter } from "expo-router"; +import { useFocusEffect, useRouter } from "expo-router"; import { MaterialCommunityIcons } from "@expo/vector-icons"; +import { isDevelopmentProvisioningProfile } from "modules/check-ios-certificate"; import { useTheme, YStack } from "tamagui"; + + import type { ScrapeMedia } from "@movie-web/provider-utils"; + + import { DownloadItem } from "~/components/DownloadItem"; import ScreenLayout from "~/components/layout/ScreenLayout"; import { MWButton } from "~/components/ui/Button"; import { useDownloadManager } from "~/hooks/DownloadManagerContext"; import { usePlayerStore } from "~/stores/player/store"; + const DownloadsScreen: React.FC = () => { const { startDownload, downloads } = useDownloadManager(); const resetVideo = usePlayerStore((state) => state.resetVideo); @@ -20,6 +27,23 @@ const DownloadsScreen: React.FC = () => { const router = useRouter(); const theme = useTheme(); + useFocusEffect( + React.useCallback(() => { + if (Platform.OS === "ios" && !isDevelopmentProvisioningProfile()) { + Alert.alert( + "Production Certificate", + "Download functionality is not available when the application is signed with a distribution certificate.", + [ + { + text: "OK", + onPress: () => router.back(), + }, + ], + ); + } + }, [router]), + ); + const handlePress = (asset?: Asset) => { if (!asset) return; resetVideo(); @@ -44,6 +68,9 @@ const DownloadsScreen: React.FC = () => { tmdbId: "98765", }, }; + + console.log(isDevelopmentProvisioningProfile()); + return ( @@ -101,4 +128,4 @@ const DownloadsScreen: React.FC = () => { ); }; -export default DownloadsScreen; +export default DownloadsScreen; \ No newline at end of file diff --git a/apps/expo/src/components/player/BottomControls.tsx b/apps/expo/src/components/player/BottomControls.tsx index 70de673..8e9325c 100644 --- a/apps/expo/src/components/player/BottomControls.tsx +++ b/apps/expo/src/components/player/BottomControls.tsx @@ -1,5 +1,6 @@ import { useCallback, useMemo, useState } from "react"; -import { TouchableOpacity } from "react-native"; +import { Platform, TouchableOpacity } from "react-native"; +import { isDevelopmentProvisioningProfile } from "modules/check-ios-certificate"; import { Text, View } from "tamagui"; import { usePlayerStore } from "~/stores/player/store"; @@ -84,7 +85,10 @@ export const BottomControls = ({ isLocalAsset }: { isLocalAsset: boolean }) => { - + {Platform.OS === "android" || + (Platform.OS === "ios" && isDevelopmentProvisioningProfile()) ? ( + + ) : null} )} diff --git a/apps/expo/tsconfig.json b/apps/expo/tsconfig.json index d80e82a..ef8c0a5 100644 --- a/apps/expo/tsconfig.json +++ b/apps/expo/tsconfig.json @@ -7,6 +7,7 @@ "paths": { "~/*": ["./src/*"], "~/components/*": ["./src/components/*"], + "modules/*": ["./modules/*"], }, "jsx": "react-native", "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",