feat: gate download behind development certificate on iOS

This commit is contained in:
Adrian Castro
2024-04-01 23:44:49 +02:00
parent 683cab9796
commit 7b1dd8170d
11 changed files with 181 additions and 4 deletions

2
.gitignore vendored
View File

@@ -19,6 +19,8 @@ expo-env.d.ts
apps/expo/.gitignore
ios/
android/
!modules/*/ios/
!modules/*/android/
# production
build

60
.vscode/launch.json vendored Normal file
View File

@@ -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": [
"<node_internals>/**"
],
"type": "node"
},
{
"name": "Run Android",
"request": "launch",
"runtimeArgs": [
"android",
],
"cwd": "${workspaceFolder}/apps/expo",
"runtimeExecutable": "pnpm",
"skipFiles": [
"<node_internals>/**"
],
"type": "node"
},
{
"name": "Build IPA",
"request": "launch",
"runtimeArgs": [
"ipa",
],
"cwd": "${workspaceFolder}/apps/expo",
"runtimeExecutable": "pnpm",
"skipFiles": [
"<node_internals>/**"
],
"type": "node"
},
{
"name": "Build APK",
"request": "launch",
"runtimeArgs": [
"apk",
],
"cwd": "${workspaceFolder}/apps/expo",
"runtimeExecutable": "pnpm",
"skipFiles": [
"<node_internals>/**"
],
"type": "node"
},
]
}

View File

@@ -24,5 +24,8 @@
],
"[github-actions-workflow]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
}
}

View File

@@ -0,0 +1,6 @@
{
"platforms": ["ios"],
"ios": {
"modules": ["CheckIosCertificateModule"]
}
}

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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("<key>get-task-allow</key><true/>")
} catch {
// Handling error if the file read fails
print("Error reading provisioning profile: \(error)")
return false
}
#endif
}
}
}

View File

@@ -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");

View File

@@ -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 (
<ScreenLayout>
<YStack gap={2} style={{ padding: 10 }}>

View File

@@ -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 }) => {
<AudioTrackSelector />
<PlaybackSpeedSelector />
<QualitySelector />
{Platform.OS === "android" ||
(Platform.OS === "ios" && isDevelopmentProvisioningProfile()) ? (
<DownloadButton />
) : null}
</>
)}
</View>

View File

@@ -7,6 +7,7 @@
"paths": {
"~/*": ["./src/*"],
"~/components/*": ["./src/components/*"],
"modules/*": ["./modules/*"],
},
"jsx": "react-native",
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",