mirror of
https://github.com/movie-web/native-app.git
synced 2025-09-13 14:33:26 +00:00
feat: gate download behind development certificate on iOS
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -19,6 +19,8 @@ expo-env.d.ts
|
|||||||
apps/expo/.gitignore
|
apps/expo/.gitignore
|
||||||
ios/
|
ios/
|
||||||
android/
|
android/
|
||||||
|
!modules/*/ios/
|
||||||
|
!modules/*/android/
|
||||||
|
|
||||||
# production
|
# production
|
||||||
build
|
build
|
||||||
|
60
.vscode/launch.json
vendored
Normal file
60
.vscode/launch.json
vendored
Normal 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"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -24,5 +24,8 @@
|
|||||||
],
|
],
|
||||||
"[github-actions-workflow]": {
|
"[github-actions-workflow]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[jsonc]": {
|
||||||
|
"editor.defaultFormatter": "vscode.json-language-features"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"platforms": ["ios"],
|
||||||
|
"ios": {
|
||||||
|
"modules": ["CheckIosCertificateModule"]
|
||||||
|
}
|
||||||
|
}
|
11
apps/expo/modules/check-ios-certificate/index.ts
Normal file
11
apps/expo/modules/check-ios-certificate/index.ts
Normal 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();
|
||||||
|
}
|
@@ -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
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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");
|
@@ -1,18 +1,25 @@
|
|||||||
import type { Asset } from "expo-media-library";
|
import type { Asset } from "expo-media-library";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { Alert, Platform } from "react-native";
|
||||||
import { ScrollView } from "react-native-gesture-handler";
|
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 { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||||
|
import { isDevelopmentProvisioningProfile } from "modules/check-ios-certificate";
|
||||||
import { useTheme, YStack } from "tamagui";
|
import { useTheme, YStack } from "tamagui";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import type { ScrapeMedia } from "@movie-web/provider-utils";
|
import type { ScrapeMedia } from "@movie-web/provider-utils";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import { DownloadItem } from "~/components/DownloadItem";
|
import { DownloadItem } from "~/components/DownloadItem";
|
||||||
import ScreenLayout from "~/components/layout/ScreenLayout";
|
import ScreenLayout from "~/components/layout/ScreenLayout";
|
||||||
import { MWButton } from "~/components/ui/Button";
|
import { MWButton } from "~/components/ui/Button";
|
||||||
import { useDownloadManager } from "~/hooks/DownloadManagerContext";
|
import { useDownloadManager } from "~/hooks/DownloadManagerContext";
|
||||||
import { usePlayerStore } from "~/stores/player/store";
|
import { usePlayerStore } from "~/stores/player/store";
|
||||||
|
|
||||||
|
|
||||||
const DownloadsScreen: React.FC = () => {
|
const DownloadsScreen: React.FC = () => {
|
||||||
const { startDownload, downloads } = useDownloadManager();
|
const { startDownload, downloads } = useDownloadManager();
|
||||||
const resetVideo = usePlayerStore((state) => state.resetVideo);
|
const resetVideo = usePlayerStore((state) => state.resetVideo);
|
||||||
@@ -20,6 +27,23 @@ const DownloadsScreen: React.FC = () => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const theme = useTheme();
|
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) => {
|
const handlePress = (asset?: Asset) => {
|
||||||
if (!asset) return;
|
if (!asset) return;
|
||||||
resetVideo();
|
resetVideo();
|
||||||
@@ -44,6 +68,9 @@ const DownloadsScreen: React.FC = () => {
|
|||||||
tmdbId: "98765",
|
tmdbId: "98765",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log(isDevelopmentProvisioningProfile());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScreenLayout>
|
<ScreenLayout>
|
||||||
<YStack gap={2} style={{ padding: 10 }}>
|
<YStack gap={2} style={{ padding: 10 }}>
|
||||||
@@ -101,4 +128,4 @@ const DownloadsScreen: React.FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DownloadsScreen;
|
export default DownloadsScreen;
|
@@ -1,5 +1,6 @@
|
|||||||
import { useCallback, useMemo, useState } from "react";
|
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 { Text, View } from "tamagui";
|
||||||
|
|
||||||
import { usePlayerStore } from "~/stores/player/store";
|
import { usePlayerStore } from "~/stores/player/store";
|
||||||
@@ -84,7 +85,10 @@ export const BottomControls = ({ isLocalAsset }: { isLocalAsset: boolean }) => {
|
|||||||
<AudioTrackSelector />
|
<AudioTrackSelector />
|
||||||
<PlaybackSpeedSelector />
|
<PlaybackSpeedSelector />
|
||||||
<QualitySelector />
|
<QualitySelector />
|
||||||
<DownloadButton />
|
{Platform.OS === "android" ||
|
||||||
|
(Platform.OS === "ios" && isDevelopmentProvisioningProfile()) ? (
|
||||||
|
<DownloadButton />
|
||||||
|
) : null}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
@@ -7,6 +7,7 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
"~/*": ["./src/*"],
|
"~/*": ["./src/*"],
|
||||||
"~/components/*": ["./src/components/*"],
|
"~/components/*": ["./src/components/*"],
|
||||||
|
"modules/*": ["./modules/*"],
|
||||||
},
|
},
|
||||||
"jsx": "react-native",
|
"jsx": "react-native",
|
||||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
|
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json",
|
||||||
|
Reference in New Issue
Block a user