@@ -14,6 +14,7 @@ import app.rive.runtime.kotlin.controllers.RiveFileController
1414import app.rive.runtime.kotlin.core.*
1515import app.rive.runtime.kotlin.core.errors.*
1616import app.rive.runtime.kotlin.renderers.PointerEvents
17+ import com.android.volley.DefaultRetryPolicy
1718import com.android.volley.NetworkResponse
1819import com.android.volley.ParseError
1920import com.android.volley.Request
@@ -716,9 +717,33 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
716717 }
717718
718719
719- private fun setUrlRiveResource (url : String , autoplay : Boolean = this.autoplay ) {
720+ private fun setUrlRiveResource (url : String ) {
720721 downloadUrlAsset(url) { bytes ->
721722 try {
723+ // Validate that we have valid content before attempting to create Rive file
724+ if (bytes.isEmpty()) {
725+ if (isUserHandlingErrors) {
726+ val rnRiveError = RNRiveError .IncorrectRiveFileUrl
727+ rnRiveError.message = " Downloaded file is empty from: $url "
728+ sendErrorToRN(rnRiveError)
729+ } else {
730+ showRNRiveError(" Downloaded file is empty from: $url " , null )
731+ }
732+ return @downloadUrlAsset
733+ }
734+
735+ // Basic validation - check if the content starts with the Rive file signature
736+ if (! isValidRiveContent(bytes)) {
737+ if (isUserHandlingErrors) {
738+ val rnRiveError = RNRiveError .MalformedFile
739+ rnRiveError.message = " Downloaded content is not a valid Rive file from: $url "
740+ sendErrorToRN(rnRiveError)
741+ } else {
742+ showRNRiveError(" Downloaded content is not a valid Rive file from: $url " , null )
743+ }
744+ return @downloadUrlAsset
745+ }
746+
722747 riveAnimationView?.setRiveBytes(
723748 bytes,
724749 fit = this .fit,
@@ -737,6 +762,42 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
737762 }
738763 }
739764
765+ /* *
766+ * Validates if the downloaded content is a valid Rive file by checking file signatures
767+ */
768+ private fun isValidRiveContent (bytes : ByteArray ): Boolean {
769+ if (bytes.size < 4 ) return false
770+
771+ // Check for Rive file signature (RIVE magic number)
772+ // Rive files start with specific byte patterns
773+ val header = bytes.take(4 ).toByteArray()
774+
775+ // Check for "RIVE" header (0x52495645)
776+ if (header[0 ] == 0x52 .toByte() &&
777+ header[1 ] == 0x49 .toByte() &&
778+ header[2 ] == 0x56 .toByte() &&
779+ header[3 ] == 0x45 .toByte()) {
780+ return true
781+ }
782+
783+ // Additional validation - check for common non-Rive content patterns
784+ val headerString = String (header, Charsets .UTF_8 )
785+
786+ // Check if it's HTML (error pages)
787+ if (headerString.startsWith(" <!DO" ) || headerString.startsWith(" <htm" )) {
788+ return false
789+ }
790+
791+ // Check if it's JSON (API error responses)
792+ if (headerString.startsWith(" {" ) || headerString.startsWith(" [" )) {
793+ return false
794+ }
795+
796+ // If we can't definitively identify it as non-Rive, let the Rive runtime validate it
797+ // This allows for different Rive file formats/versions
798+ return true
799+ }
800+
740801 fun setArtboardName (artboardName : String ) {
741802 try {
742803 this .artboardName = artboardName
@@ -993,11 +1054,45 @@ class RiveReactNativeView(private val context: ThemedReactContext) : FrameLayout
9931054 return
9941055 }
9951056
1057+ // Properly encode the URL to handle spaces and special characters
1058+ val encodedUrl = try {
1059+ val uri = Uri .parse(url)
1060+ uri.toString() // This will properly encode the URL
1061+ } catch (e: Exception ) {
1062+ handleInvalidUrlError(url)
1063+ return
1064+ }
1065+
9961066 val queue = Volley .newRequestQueue(context)
9971067
9981068 val stringRequest = RNRiveFileRequest (
999- url, listener
1000- ) { error -> handleURLAssetError(url, error, isUserHandlingErrors) }
1069+ encodedUrl, listener
1070+ ) { error ->
1071+ // Enhanced error handling for better debugging
1072+ val errorMessage = when {
1073+ error.networkResponse?.statusCode == 404 -> " File not found (404) at: $url "
1074+ error.networkResponse?.statusCode == 403 -> " Access forbidden (403) for: $url "
1075+ error.networkResponse?.statusCode == 500 -> " Server error (500) for: $url "
1076+ error.cause is java.net.SocketTimeoutException -> " Timeout downloading from: $url "
1077+ error.cause is java.net.UnknownHostException -> " Cannot resolve host for: $url "
1078+ else -> " Unable to download the Rive asset file from: $url "
1079+ }
1080+
1081+ if (isUserHandlingErrors) {
1082+ val rnRiveError = RNRiveError .IncorrectRiveFileUrl
1083+ rnRiveError.message = errorMessage
1084+ sendErrorToRN(rnRiveError)
1085+ } else {
1086+ showRNRiveError(errorMessage, error)
1087+ }
1088+ }
1089+
1090+ // Add timeout to prevent hanging requests
1091+ stringRequest.retryPolicy = DefaultRetryPolicy (
1092+ 15000 , // 15 second timeout
1093+ 1 , // no retries
1094+ DefaultRetryPolicy .DEFAULT_BACKOFF_MULT
1095+ )
10011096
10021097 queue.add(stringRequest)
10031098 }
0 commit comments