1 // Copyright 2015-present 650 Industries. All rights reserved. 2 package host.exp.exponent.exceptions 3 4 import host.exp.exponent.Constants 5 import host.exp.expoview.ExpoViewBuildConfig 6 import org.json.JSONArray 7 import org.json.JSONException 8 import org.json.JSONObject 9 import java.lang.Exception 10 import java.lang.NumberFormatException 11 12 class ManifestException : ExponentException { 13 private val manifestUrl: String 14 private var errorJSON: JSONObject? = null 15 val errorHeader: String? 16 get() = errorJSON?.let { 17 try { 18 when (it.getString("errorCode")) { 19 "EXPERIENCE_SDK_VERSION_OUTDATED" -> "Project is incompatible with this version of Expo Go" 20 "EXPERIENCE_SDK_VERSION_TOO_NEW" -> "Project is incompatible with this version of Expo Go" 21 "SNACK_NOT_FOUND_FOR_SDK_VERSION" -> "This Snack is incompatible with this version of Expo Go" 22 else -> null 23 } 24 } catch (e: JSONException) { 25 null 26 } 27 } 28 29 constructor(originalException: Exception?, manifestUrl: String) : super(originalException) { 30 this.manifestUrl = manifestUrl 31 this.errorJSON = null 32 } 33 34 constructor(originalException: Exception?, manifestUrl: String, errorJSON: JSONObject?) : super( 35 originalException 36 ) { 37 this.errorJSON = errorJSON 38 this.manifestUrl = manifestUrl 39 } 40 41 override fun toString(): String { 42 val extraMessage = if (ExpoViewBuildConfig.DEBUG) { 43 // This will get hit in a detached app. 44 " Are you sure expo-cli is running?" 45 } else { 46 "" 47 } 48 49 return when (manifestUrl) { 50 Constants.INITIAL_URL -> "Could not load app.$extraMessage" 51 else -> { 52 var formattedMessage = "Could not load $manifestUrl.$extraMessage" 53 val supportedSdks = Constants.SDK_VERSIONS_LIST.map { 54 it.substring(0, it.indexOf('.')).toInt() 55 }.sorted() 56 val supportedSdksString = { conjunction: String -> 57 supportedSdks.subList(0, supportedSdks.size - 1) 58 .joinToString(", ") + " $conjunction ${supportedSdks.last()}" 59 } 60 61 if (errorJSON != null) { 62 try { 63 val errorCode = errorJSON!!.getString("errorCode") 64 val rawMessage = errorJSON!!.getString("message") 65 when (errorCode) { 66 "EXPERIENCE_NOT_FOUND", // Really doesn't exist 67 "EXPERIENCE_NOT_PUBLISHED_ERROR", // Not published 68 "EXPERIENCE_RELEASE_NOT_FOUND_ERROR" -> // Can't find a release for the requested release channel 69 formattedMessage = 70 "No project found at $manifestUrl." 71 "EXPERIENCE_SDK_VERSION_OUTDATED" -> { 72 val metadata = errorJSON!!.getJSONObject("metadata") 73 val availableSDKVersions = metadata.getJSONArray("availableSDKVersions") 74 val sdkVersionRequired = availableSDKVersions.getString(0).let { 75 it.substring(0, it.indexOf('.')) 76 } 77 78 formattedMessage = 79 "This project uses SDK $sdkVersionRequired, but this version of Expo Go supports only SDKs ${supportedSdksString("and")}.<br><br>" + 80 "To open this project:<br>" + 81 "• Update it to SDK ${supportedSdksString("or")}.<br>" + 82 "• Install an older version of Expo Go that supports the project's SDK version.<br><br>" + 83 "If you are unsure how to update the project or install a suitable version of Expo Go, refer to the <a href='https://docs.expo.dev/get-started/expo-go/#sdk-versions'>SDK Versions Guide</a>." 84 } 85 "SNACK_NOT_FOUND_FOR_SDK_VERSION" -> { 86 formattedMessage = 87 "Incompatible SDK version or no SDK version specified. This version of Expo Go only supports the following SDKs (runtimes): " + Constants.SDK_VERSIONS_LIST.joinToString() + ". A development build must be used to load other runtimes.<br><a href='https://docs.expo.dev/develop/development-builds/introduction/'>Learn more about development builds</a>." 88 } 89 "EXPERIENCE_SDK_VERSION_TOO_NEW" -> 90 formattedMessage = 91 "This project requires a newer version of Expo Go - please download the latest version from the Play Store." 92 "EXPERIENCE_NOT_VIEWABLE" -> 93 formattedMessage = 94 rawMessage // From server: The experience you requested is not viewable by you. You will need to log in or ask the owner to grant you access. 95 "USER_SNACK_NOT_FOUND", "SNACK_NOT_FOUND" -> 96 formattedMessage = 97 "No snack found at $manifestUrl." 98 "SNACK_RUNTIME_NOT_RELEASED" -> 99 formattedMessage = 100 rawMessage // From server: `The Snack runtime for corresponding sdk version of this Snack ("${sdkVersions[0]}") is not released.`, 101 "SNACK_NOT_FOUND_FOR_SDK_VERSION" -> run closure@{ 102 val metadata = errorJSON!!.getJSONObject("metadata") 103 val fullName = metadata["fullName"] ?: "" 104 val snackSdkVersion = 105 (metadata["sdkVersions"] as? JSONArray)?.get(0) as? String ?: "unknown" 106 107 if (snackSdkVersion == "unknown" || snackSdkVersion.indexOf(".") == -1) { 108 formattedMessage = rawMessage 109 return@closure 110 } 111 112 val snackSdkVersionValue = try { 113 Integer.parseInt(snackSdkVersion.substring(0, snackSdkVersion.indexOf("."))) 114 } catch (e: NumberFormatException) { 115 formattedMessage = rawMessage 116 return@closure 117 } 118 formattedMessage = 119 "The snack \"${fullName}\" was found, but it is not compatible with your version of Expo Go. It was released for SDK $snackSdkVersionValue, but your Expo Go supports only SDKs ${supportedSdksString("and")}." 120 formattedMessage += if (supportedSdks.last() < snackSdkVersionValue) { 121 "<br><br>You need to update your Expo Go app in order to run this Snack." 122 } else { 123 "<br><br>Snack needs to be upgraded to a current SDK version. To do it, open the project at <a href='https://snack.expo.dev'>Expo Snack website</a>. It will be automatically upgraded to a supported SDK version." 124 } 125 formattedMessage += "<br><br><a href='https://docs.expo.dev/get-started/expo-go/#sdk-versions'>Learn more about SDK versions and Expo Go</a>." 126 } 127 } 128 } catch (e: JSONException) { 129 return formattedMessage 130 } 131 } 132 formattedMessage 133 } 134 } 135 } 136 } 137