mirror of
https://github.com/anultravioletaurora/Jellify.git
synced 2025-12-19 04:19:44 -06:00
feat: Add URL validation before file downloads and ensure valid session IDs for tracks and streams. (#813)
This commit is contained in:
@@ -60,6 +60,11 @@ export async function downloadJellyfinFile(
|
|||||||
setDownloadProgress: JellifyDownloadProgressState,
|
setDownloadProgress: JellifyDownloadProgressState,
|
||||||
preferredExtension?: string | null,
|
preferredExtension?: string | null,
|
||||||
): Promise<DownloadedFileInfo> {
|
): Promise<DownloadedFileInfo> {
|
||||||
|
// Validate URL before attempting download to prevent NPE in native code
|
||||||
|
if (!url || url.trim() === '') {
|
||||||
|
throw new Error('Invalid download URL: URL is empty or undefined')
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const urlExtension = normalizeExtension(getExtensionFromUrl(url))
|
const urlExtension = normalizeExtension(getExtensionFromUrl(url))
|
||||||
const hintedExtension = normalizeExtension(preferredExtension)
|
const hintedExtension = normalizeExtension(preferredExtension)
|
||||||
@@ -168,6 +173,12 @@ export const saveAudio = async (
|
|||||||
//Ignore
|
//Ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate track URL before attempting download
|
||||||
|
if (!track.url || track.url.trim() === '') {
|
||||||
|
console.error('Cannot download track: URL is missing', track.item.Id)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const downloadedTrackFile = await downloadJellyfinFile(
|
const downloadedTrackFile = await downloadJellyfinFile(
|
||||||
track.url,
|
track.url,
|
||||||
@@ -177,10 +188,11 @@ export const saveAudio = async (
|
|||||||
track.mediaSourceInfo?.Container,
|
track.mediaSourceInfo?.Container,
|
||||||
)
|
)
|
||||||
let downloadedArtworkFile: DownloadedFileInfo | undefined
|
let downloadedArtworkFile: DownloadedFileInfo | undefined
|
||||||
if (track.artwork) {
|
// Check for non-empty artwork URL (empty string passes truthy check but fails download)
|
||||||
|
if (track.artwork && typeof track.artwork === 'string' && track.artwork.trim() !== '') {
|
||||||
downloadedArtworkFile = await downloadJellyfinFile(
|
downloadedArtworkFile = await downloadJellyfinFile(
|
||||||
track.artwork as string,
|
track.artwork,
|
||||||
track.item.Id as string,
|
`${track.item.Id}-artwork`,
|
||||||
track.title as string,
|
track.title as string,
|
||||||
setDownloadProgress,
|
setDownloadProgress,
|
||||||
undefined,
|
undefined,
|
||||||
|
|||||||
@@ -23,6 +23,18 @@ import StreamingQuality from '../enums/audio-quality'
|
|||||||
import { getAudioCache } from '../api/mutations/download/offlineModeUtils'
|
import { getAudioCache } from '../api/mutations/download/offlineModeUtils'
|
||||||
import RNFS from 'react-native-fs'
|
import RNFS from 'react-native-fs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures a valid session ID is returned.
|
||||||
|
* The ?? operator doesn't catch empty strings, so we need this helper.
|
||||||
|
* Empty session IDs cause MusicService to crash with "Session ID must be unique. ID="
|
||||||
|
*/
|
||||||
|
function getValidSessionId(sessionId: string | null | undefined): string {
|
||||||
|
if (sessionId && sessionId.trim() !== '') {
|
||||||
|
return sessionId
|
||||||
|
}
|
||||||
|
return uuid.v4().toString()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the artwork URL for a track, prioritizing the track's own artwork over the album's artwork.
|
* Gets the artwork URL for a track, prioritizing the track's own artwork over the album's artwork.
|
||||||
* Falls back to artist image if no album artwork is available.
|
* Falls back to artist image if no album artwork is available.
|
||||||
@@ -169,16 +181,21 @@ function ensureFileUri(path?: string): string | undefined {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildDownloadedTrack(downloadedTrack: JellifyDownload): TrackMediaInfo {
|
function buildDownloadedTrack(downloadedTrack: JellifyDownload): TrackMediaInfo {
|
||||||
|
// Safely build the image path - artwork is optional and may be undefined
|
||||||
|
const imagePath = downloadedTrack.artwork
|
||||||
|
? `file://${RNFS.DocumentDirectoryPath}/${downloadedTrack.artwork.split('/').pop()}`
|
||||||
|
: undefined
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: TrackType.Default,
|
type: TrackType.Default,
|
||||||
url: `file://${RNFS.DocumentDirectoryPath}/${downloadedTrack.path!.split('/').pop()}`,
|
url: `file://${RNFS.DocumentDirectoryPath}/${downloadedTrack.path!.split('/').pop()}`,
|
||||||
image: `file://${RNFS.DocumentDirectoryPath}/${downloadedTrack.artwork!.split('/').pop()}`,
|
image: imagePath,
|
||||||
duration: convertRunTimeTicksToSeconds(
|
duration: convertRunTimeTicksToSeconds(
|
||||||
downloadedTrack.mediaSourceInfo?.RunTimeTicks || downloadedTrack.item.RunTimeTicks || 0,
|
downloadedTrack.mediaSourceInfo?.RunTimeTicks || downloadedTrack.item.RunTimeTicks || 0,
|
||||||
),
|
),
|
||||||
item: downloadedTrack.item,
|
item: downloadedTrack.item,
|
||||||
mediaSourceInfo: downloadedTrack.mediaSourceInfo,
|
mediaSourceInfo: downloadedTrack.mediaSourceInfo,
|
||||||
sessionId: downloadedTrack.sessionId,
|
sessionId: getValidSessionId(downloadedTrack.sessionId),
|
||||||
sourceType: 'download',
|
sourceType: 'download',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -198,7 +215,7 @@ function buildTranscodedTrack(
|
|||||||
duration: convertRunTimeTicksToSeconds(RunTimeTicks ?? 0),
|
duration: convertRunTimeTicksToSeconds(RunTimeTicks ?? 0),
|
||||||
mediaSourceInfo,
|
mediaSourceInfo,
|
||||||
item,
|
item,
|
||||||
sessionId,
|
sessionId: getValidSessionId(sessionId),
|
||||||
sourceType: 'stream',
|
sourceType: 'stream',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,7 +245,7 @@ function buildAudioApiUrl(
|
|||||||
const mediaSource = mediaInfo!.MediaSources![0]
|
const mediaSource = mediaInfo!.MediaSources![0]
|
||||||
|
|
||||||
urlParams = {
|
urlParams = {
|
||||||
playSessionId: mediaInfo?.PlaySessionId ?? uuid.v4(),
|
playSessionId: getValidSessionId(mediaInfo?.PlaySessionId),
|
||||||
startTimeTicks: '0',
|
startTimeTicks: '0',
|
||||||
static: 'true',
|
static: 'true',
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user