This commit is contained in:
tapframe 2025-09-06 23:42:27 +05:30
parent 019f99c956
commit b1494e8a1d
7 changed files with 153 additions and 95 deletions

View file

@ -1,8 +1,9 @@
#!/bin/bash #!/bin/bash
# Check if the correct number of arguments are provided # Check if the correct number of arguments are provided
if [ "$#" -ne 2 ]; then if [ "$#" -ne 1 ]; then
echo "Usage: $0 <runtimeVersion> <xavia-ota-url>" echo "Usage: $0 <xavia-ota-url>"
echo "Example: $0 https://grim-reyna-tapframe-69970143.koyeb.app"
exit 1 exit 1
fi fi
@ -10,52 +11,150 @@ fi
commitHash=$(git rev-parse HEAD) commitHash=$(git rev-parse HEAD)
commitMessage=$(git log -1 --pretty=%B) commitMessage=$(git log -1 --pretty=%B)
# Check if app.json exists
if [ ! -f "app.json" ]; then
echo "Error: app.json not found in current directory"
exit 1
fi
# Auto-detect runtime version from app.json
runtimeVersion=$(jq -r '.expo.runtimeVersion' app.json)
if [ "$runtimeVersion" = "null" ] || [ -z "$runtimeVersion" ]; then
echo "Error: Could not find runtimeVersion in app.json"
echo "Please ensure app.json contains: \"runtimeVersion\": \"your-version\""
exit 1
fi
# Assign arguments to variables # Assign arguments to variables
runtimeVersion=$1 serverHost=$1
serverHost=$2
# Validate server URL format
if [[ ! "$serverHost" =~ ^https?:// ]]; then
echo "Error: Server URL must start with http:// or https://"
echo "Example: https://grim-reyna-tapframe-69970143.koyeb.app"
exit 1
fi
# Generate a timestamp for the output folder # Generate a timestamp for the output folder
timestamp=$(date -u +%Y%m%d%H%M%S) timestamp=$(date -u +%Y%m%d%H%M%S)
outputFolder="ota-builds/$timestamp" outputFolder="ota-builds/$timestamp"
# Ask the user to confirm the hash, commit message, runtime version, and output folder # Display build information
echo "Output Folder: $outputFolder" echo "🚀 Nuvio OTA Build & Deploy Script"
echo "Runtime Version: $runtimeVersion" echo "=================================="
echo "Commit Hash: $commitHash" echo "📁 Output Folder: $outputFolder"
echo "Commit Message: $commitMessage" echo "📱 Runtime Version: $runtimeVersion"
echo "🔗 Commit Hash: $commitHash"
echo "📝 Commit Message: $commitMessage"
echo "🌐 Server URL: $serverHost"
echo ""
read -p "Do you want to proceed with these values? (y/n): " confirm read -p "Do you want to proceed with these values? (y/n): " confirm
if [ "$confirm" != "y" ]; then if [ "$confirm" != "y" ]; then
echo "Operation cancelled by the user." echo "Operation cancelled by the user."
exit 1 exit 1
fi fi
echo "🔨 Starting build process..."
# Clean up any existing output folder
rm -rf $outputFolder rm -rf $outputFolder
mkdir -p $outputFolder mkdir -p $outputFolder
# Run expo export with the specified output folder # Run expo export with the specified output folder
npx expo export --output-dir $outputFolder echo "📦 Exporting Expo bundle..."
if ! npx expo export --output-dir $outputFolder; then
echo "❌ Error: Expo export failed"
exit 1
fi
# Extract expo config property from app.json and save to expoconfig.json # Extract expo config property from app.json and save to expoconfig.json
echo "⚙️ Extracting Expo configuration..."
jq '.expo' app.json > $outputFolder/expoconfig.json jq '.expo' app.json > $outputFolder/expoconfig.json
# Zip the output folder # Zip the output folder
echo "📦 Creating deployment package..."
cd $outputFolder cd $outputFolder
zip -q -r ${timestamp}.zip . if ! zip -q -r ${timestamp}.zip .; then
echo "❌ Error: Failed to create zip file"
exit 1
fi
# Upload the zip file to the server # Upload the zip file to the server
curl -X POST $serverHost/api/upload -F "file=@${timestamp}.zip" -F "runtimeVersion=$runtimeVersion" -F "commitHash=$commitHash" -F "commitMessage=$commitMessage" echo "🚀 Uploading to server..."
echo "📊 File size: $(du -h ${timestamp}.zip | cut -f1)"
# Check server health before upload
echo "🔍 Checking server status..."
if ! curl --max-time 10 --connect-timeout 5 -s -o /dev/null "$serverHost/api/manifest"; then
echo "⚠️ Warning: Server may be slow or unresponsive"
echo "💡 Proceeding with upload anyway..."
else
echo "✅ Server is responding"
fi
echo "" echo ""
echo "Uploaded to $serverHost/api/upload" # Try upload with extended timeout and retry logic
max_retries=3
retry_count=0
while [ $retry_count -lt $max_retries ]; do
echo "🔄 Upload attempt $((retry_count + 1))/$max_retries..."
response=$(curl --max-time 300 --connect-timeout 30 -X POST $serverHost/api/upload \
-F "file=@${timestamp}.zip" \
-F "runtimeVersion=$runtimeVersion" \
-F "commitHash=$commitHash" \
-F "commitMessage=$commitMessage" \
--write-out "HTTP_CODE:%{http_code}" \
--silent \
--show-error)
# Extract HTTP code from response
http_code=$(echo "$response" | grep -o "HTTP_CODE:[0-9]*" | cut -d: -f2)
# Check if we got a valid HTTP code
if [ -z "$http_code" ] || ! [[ "$http_code" =~ ^[0-9]+$ ]]; then
echo "❌ Failed to extract HTTP status code from response"
echo "Response: $response"
http_code="000"
fi
echo "HTTP Status: $http_code"
if [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then
echo ""
echo "✅ Successfully uploaded to $serverHost/api/upload"
break
else
retry_count=$((retry_count + 1))
if [ $retry_count -lt $max_retries ]; then
echo "⚠️ Upload attempt $retry_count failed, retrying in 5 seconds..."
sleep 5
else
echo "❌ Error: Upload failed after $max_retries attempts"
echo "📊 Final HTTP Status: $http_code"
if [ "$http_code" = "524" ]; then
echo "💡 Error 524: Server timeout - try again later or check server capacity"
elif [ "$http_code" = "413" ]; then
echo "💡 Error 413: File too large - consider reducing bundle size"
elif [ "$http_code" = "500" ]; then
echo "💡 Error 500: Server error - check server logs"
else
echo "💡 Check server status and try again"
fi
exit 1
fi
fi
done
cd .. cd ..
# Remove the output folder and zip file # Remove the output folder and zip file
echo "🧹 Cleaning up temporary files..."
rm -rf $outputFolder rm -rf $outputFolder
echo "Removed $outputFolder" echo "🎉 Build and deployment completed successfully!"
echo "Done" echo "📱 Runtime Version: $runtimeVersion"
echo "🔗 Commit: $commitHash"

7
manifest_response.json Normal file
View file

@ -0,0 +1,7 @@
----------------------------568300859475270590089014
Content-Disposition: form-data; name="directive"
Content-Type: application/json
content-type: application/json; charset=utf-8
{"type":"noUpdateAvailable"}
----------------------------568300859475270590089014--

View file

@ -0,0 +1,7 @@
----------------------------694338510290346396309710
Content-Disposition: form-data; name="directive"
Content-Type: application/json
content-type: application/json; charset=utf-8
{"type":"noUpdateAvailable"}
----------------------------694338510290346396309710--

View file

@ -72,7 +72,7 @@ const UpdatePopup: React.FC<UpdatePopupProps> = ({
<View style={[ <View style={[
styles.popup, styles.popup,
{ {
backgroundColor: currentTheme.colors.elevation1 || '#1a1a1a', backgroundColor: currentTheme.colors.darkBackground || '#1a1a1a',
borderColor: currentTheme.colors.elevation2 || '#333333', borderColor: currentTheme.colors.elevation2 || '#333333',
marginTop: insets.top + 20, marginTop: insets.top + 20,
marginBottom: insets.bottom + 20, marginBottom: insets.bottom + 20,
@ -173,8 +173,8 @@ const UpdatePopup: React.FC<UpdatePopupProps> = ({
styles.button, styles.button,
styles.secondaryButton, styles.secondaryButton,
{ {
backgroundColor: currentTheme.colors.elevation2, backgroundColor: currentTheme.colors.darkBackground || '#2a2a2a',
borderColor: currentTheme.colors.elevation3, borderColor: currentTheme.colors.elevation3 || '#444444',
} }
]} ]}
onPress={handleUpdateLater} onPress={handleUpdateLater}
@ -194,8 +194,8 @@ const UpdatePopup: React.FC<UpdatePopupProps> = ({
styles.button, styles.button,
styles.secondaryButton, styles.secondaryButton,
{ {
backgroundColor: currentTheme.colors.elevation2, backgroundColor: currentTheme.colors.darkBackground || '#2a2a2a',
borderColor: currentTheme.colors.elevation3, borderColor: currentTheme.colors.elevation3 || '#444444',
} }
]} ]}
onPress={handleDismiss} onPress={handleDismiss}
@ -239,7 +239,7 @@ const styles = StyleSheet.create({
width: Math.min(width - 40, 400), width: Math.min(width - 40, 400),
borderRadius: 20, borderRadius: 20,
borderWidth: 1, borderWidth: 1,
backgroundColor: '#1a1a1a', // Fallback solid background backgroundColor: '#1a1a1a', // Solid background - not transparent
shadowColor: '#000', shadowColor: '#000',
shadowOffset: { width: 0, height: 10 }, shadowOffset: { width: 0, height: 10 },
shadowOpacity: 0.5, shadowOpacity: 0.5,
@ -297,7 +297,7 @@ const styles = StyleSheet.create({
marginTop: 8, marginTop: 8,
padding: 12, padding: 12,
borderRadius: 8, borderRadius: 8,
backgroundColor: 'rgba(255, 255, 255, 0.1)', backgroundColor: 'rgba(255, 255, 255, 0.15)',
}, },
description: { description: {
fontSize: 14, fontSize: 14,

View file

@ -62,7 +62,7 @@ const createStyles = (colors: any) => StyleSheet.create({
flex: 1, flex: 1,
}, },
section: { section: {
backgroundColor: colors.elevation1, backgroundColor: colors.darkBackground,
marginBottom: 16, marginBottom: 16,
borderRadius: 12, borderRadius: 12,
padding: 16, padding: 16,
@ -181,7 +181,7 @@ const createStyles = (colors: any) => StyleSheet.create({
lineHeight: 20, lineHeight: 20,
}, },
textInput: { textInput: {
backgroundColor: colors.elevation1, backgroundColor: colors.darkBackground,
borderRadius: 8, borderRadius: 8,
padding: 12, padding: 12,
color: colors.white, color: colors.white,
@ -369,7 +369,7 @@ const createStyles = (colors: any) => StyleSheet.create({
}, },
// New styles for improved UX // New styles for improved UX
collapsibleSection: { collapsibleSection: {
backgroundColor: colors.elevation1, backgroundColor: colors.darkBackground,
marginBottom: 16, marginBottom: 16,
borderRadius: 12, borderRadius: 12,
overflow: 'hidden', overflow: 'hidden',
@ -392,7 +392,7 @@ const createStyles = (colors: any) => StyleSheet.create({
searchContainer: { searchContainer: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
backgroundColor: colors.elevation1, backgroundColor: colors.darkBackground,
borderRadius: 12, borderRadius: 12,
marginBottom: 16, marginBottom: 16,
paddingHorizontal: 12, paddingHorizontal: 12,
@ -489,7 +489,7 @@ const createStyles = (colors: any) => StyleSheet.create({
alignItems: 'center', alignItems: 'center',
}, },
modalContent: { modalContent: {
backgroundColor: colors.elevation1, backgroundColor: colors.darkBackground,
borderRadius: 16, borderRadius: 16,
padding: 24, padding: 24,
margin: 20, margin: 20,
@ -750,10 +750,7 @@ const PluginsScreen: React.FC = () => {
const [currentRepositoryId, setCurrentRepositoryId] = useState<string>(''); const [currentRepositoryId, setCurrentRepositoryId] = useState<string>('');
const [showAddRepositoryModal, setShowAddRepositoryModal] = useState(false); const [showAddRepositoryModal, setShowAddRepositoryModal] = useState(false);
const [newRepositoryUrl, setNewRepositoryUrl] = useState(''); const [newRepositoryUrl, setNewRepositoryUrl] = useState('');
const [newRepositoryName, setNewRepositoryName] = useState('');
const [newRepositoryDescription, setNewRepositoryDescription] = useState('');
const [switchingRepository, setSwitchingRepository] = useState<string | null>(null); const [switchingRepository, setSwitchingRepository] = useState<string | null>(null);
const [fetchingRepoName, setFetchingRepoName] = useState(false);
// New UX state // New UX state
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
@ -837,29 +834,8 @@ const PluginsScreen: React.FC = () => {
} }
}; };
const handleUrlChange = async (url: string) => { const handleUrlChange = (url: string) => {
setNewRepositoryUrl(url); setNewRepositoryUrl(url);
// Auto-populate repository name if it's empty and URL is valid
if (!newRepositoryName.trim() && url.trim()) {
setFetchingRepoName(true);
try {
// Try to fetch name from manifest first
const manifestName = await localScraperService.fetchRepositoryNameFromManifest(url.trim());
setNewRepositoryName(manifestName);
} catch (error) {
// Fallback to URL extraction if manifest fetch fails
try {
const extractedName = localScraperService.extractRepositoryName(url.trim());
if (extractedName !== 'Unknown Repository') {
setNewRepositoryName(extractedName);
}
} catch (extractError) {
// Ignore errors, just don't auto-populate
}
} finally {
setFetchingRepoName(false);
}
}
}; };
const handleAddRepository = async () => { const handleAddRepository = async () => {
@ -873,7 +849,7 @@ const PluginsScreen: React.FC = () => {
if (!url.startsWith('https://raw.githubusercontent.com/') && !url.startsWith('http://')) { if (!url.startsWith('https://raw.githubusercontent.com/') && !url.startsWith('http://')) {
Alert.alert( Alert.alert(
'Invalid URL Format', 'Invalid URL Format',
'Please use a valid GitHub raw URL format:\n\nhttps://raw.githubusercontent.com/username/repo/branch/\n\nExample:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/main/' 'Please use a valid GitHub raw URL format:\n\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch\n\nExample:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/master'
); );
return; return;
} }
@ -881,9 +857,9 @@ const PluginsScreen: React.FC = () => {
try { try {
setIsLoading(true); setIsLoading(true);
const repoId = await localScraperService.addRepository({ const repoId = await localScraperService.addRepository({
name: newRepositoryName.trim(), // Let the service fetch from manifest if empty name: '', // Let the service fetch from manifest
url, url,
description: newRepositoryDescription.trim(), description: '',
enabled: true enabled: true
}); });
@ -895,9 +871,6 @@ const PluginsScreen: React.FC = () => {
await loadScrapers(); await loadScrapers();
setNewRepositoryUrl(''); setNewRepositoryUrl('');
setNewRepositoryName('');
setNewRepositoryDescription('');
setFetchingRepoName(false);
setShowAddRepositoryModal(false); setShowAddRepositoryModal(false);
Alert.alert('Success', 'Repository added and refreshed successfully'); Alert.alert('Success', 'Repository added and refreshed successfully');
} catch (error) { } catch (error) {
@ -1016,7 +989,7 @@ const PluginsScreen: React.FC = () => {
if (!url.startsWith('https://raw.githubusercontent.com/') && !url.startsWith('http://')) { if (!url.startsWith('https://raw.githubusercontent.com/') && !url.startsWith('http://')) {
Alert.alert( Alert.alert(
'Invalid URL Format', 'Invalid URL Format',
'Please use a valid GitHub raw URL format:\n\nhttps://raw.githubusercontent.com/username/repo/branch/\n\nExample:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/main/' 'Please use a valid GitHub raw URL format:\n\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch\n\nExample:\nhttps://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/master'
); );
return; return;
} }
@ -1051,7 +1024,7 @@ const PluginsScreen: React.FC = () => {
const errorMessage = error instanceof Error ? error.message : String(error); const errorMessage = error instanceof Error ? error.message : String(error);
Alert.alert( Alert.alert(
'Repository Error', 'Repository Error',
`Failed to refresh repository: ${errorMessage}\n\nPlease ensure your URL is correct and follows this format:\nhttps://raw.githubusercontent.com/username/repo/branch/` `Failed to refresh repository: ${errorMessage}\n\nPlease ensure your URL is correct and follows this format:\nhttps://raw.githubusercontent.com/username/repo/refs/heads/branch`
); );
} finally { } finally {
setIsRefreshing(false); setIsRefreshing(false);
@ -1135,7 +1108,7 @@ const PluginsScreen: React.FC = () => {
}; };
const handleUseDefaultRepo = () => { const handleUseDefaultRepo = () => {
const defaultUrl = 'https://raw.githubusercontent.com/tapframe/nuvio-providers/main'; const defaultUrl = 'https://raw.githubusercontent.com/tapframe/nuvio-providers/refs/heads/master';
setRepositoryUrl(defaultUrl); setRepositoryUrl(defaultUrl);
}; };
@ -1731,53 +1704,24 @@ const PluginsScreen: React.FC = () => {
<View style={styles.modalContent}> <View style={styles.modalContent}>
<Text style={styles.modalTitle}>Add New Repository</Text> <Text style={styles.modalTitle}>Add New Repository</Text>
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: 8 }}>
<Text style={styles.settingTitle}>Repository Name</Text>
{fetchingRepoName && (
<ActivityIndicator size="small" color={colors.primary} style={{ marginLeft: 8 }} />
)}
</View>
<TextInput
style={styles.textInput}
value={newRepositoryName}
onChangeText={setNewRepositoryName}
placeholder="Enter repository name"
placeholderTextColor={colors.mediumGray}
autoCapitalize="words"
/>
<Text style={[styles.settingTitle, { marginBottom: 8 }]}>Repository URL</Text> <Text style={[styles.settingTitle, { marginBottom: 8 }]}>Repository URL</Text>
<TextInput <TextInput
style={styles.textInput} style={styles.textInput}
value={newRepositoryUrl} value={newRepositoryUrl}
onChangeText={handleUrlChange} onChangeText={handleUrlChange}
placeholder="https://raw.githubusercontent.com/username/repo/branch/" placeholder="https://raw.githubusercontent.com/username/repo/refs/heads/branch"
placeholderTextColor={colors.mediumGray} placeholderTextColor={colors.mediumGray}
autoCapitalize="none" autoCapitalize="none"
autoCorrect={false} autoCorrect={false}
keyboardType="url" keyboardType="url"
/> />
<Text style={[styles.settingTitle, { marginBottom: 8 }]}>Description (Optional)</Text>
<TextInput
style={[styles.textInput, { height: 80 }]}
value={newRepositoryDescription}
onChangeText={setNewRepositoryDescription}
placeholder="Enter repository description"
placeholderTextColor={colors.mediumGray}
multiline={true}
numberOfLines={3}
/>
<View style={styles.buttonRow}> <View style={styles.buttonRow}>
<TouchableOpacity <TouchableOpacity
style={[styles.button, styles.secondaryButton, { flex: 1 }]} style={[styles.button, styles.secondaryButton, { flex: 1 }]}
onPress={() => { onPress={() => {
setShowAddRepositoryModal(false); setShowAddRepositoryModal(false);
setNewRepositoryUrl(''); setNewRepositoryUrl('');
setNewRepositoryName('');
setNewRepositoryDescription('');
setFetchingRepoName(false);
}} }}
> >
<Text style={styles.secondaryButtonText}>Cancel</Text> <Text style={styles.secondaryButtonText}>Cancel</Text>

View file

@ -410,6 +410,7 @@ const UpdateScreen: React.FC = () => {
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
)} )}
</View> </View>
</View> </View>

@ -1 +1 @@
Subproject commit ba2fe779d75a8ed285c5d37f05532c3ff4acce22 Subproject commit ef5455acaa04404d816a6bffc25a259e28189918