2023-05-15 15:38:54 -05:00
//
// S o u r c e E r r o r . s w i f t
// A l t S t o r e C o r e
//
// C r e a t e d b y R i l e y T e s t u t o n 5 / 3 / 2 3 .
// C o p y r i g h t © 2 0 2 3 R i l e y T e s t u t . A l l r i g h t s r e s e r v e d .
//
import AltStoreCore
extension SourceError
{
enum Code : Int , ALTErrorCode
{
typealias Error = SourceError
case unsupported
case duplicateBundleID
case duplicateVersion
2023-05-15 16:25:25 -05:00
case blocked
2023-05-15 15:38:54 -05:00
case changedID
case duplicate
case missingPermissionUsageDescription
2023-10-13 13:40:08 -05:00
case missingScreenshotSize
2024-02-15 15:34:28 -06:00
2024-02-15 18:17:51 -06:00
case marketplaceNotSupported = 101
case marketplaceRequired
2023-05-15 15:38:54 -05:00
}
static func unsupported ( _ source : Source ) -> SourceError { SourceError ( code : . unsupported , source : source ) }
static func duplicateBundleID ( _ bundleID : String , source : Source ) -> SourceError { SourceError ( code : . duplicateBundleID , source : source , bundleID : bundleID ) }
static func duplicateVersion ( _ version : String , for app : StoreApp , source : Source ) -> SourceError { SourceError ( code : . duplicateVersion , source : source , app : app , version : version ) }
2023-05-16 15:39:38 -05:00
static func blocked ( _ source : Source , bundleIDs : [ String ] ? , existingSource : Source ? ) -> SourceError { SourceError ( code : . blocked , source : source , existingSource : existingSource , bundleIDs : bundleIDs ) }
2023-05-15 15:38:54 -05:00
static func changedID ( _ identifier : String , previousID : String , source : Source ) -> SourceError { SourceError ( code : . changedID , source : source , sourceID : identifier , previousSourceID : previousID ) }
2023-05-16 15:39:38 -05:00
static func duplicate ( _ source : Source , existingSource : Source ? ) -> SourceError { SourceError ( code : . duplicate , source : source , existingSource : existingSource ) }
2023-05-15 15:38:54 -05:00
static func missingPermissionUsageDescription ( for permission : any ALTAppPermission , app : StoreApp , source : Source ) -> SourceError {
SourceError ( code : . missingPermissionUsageDescription , source : source , app : app , permission : permission )
}
2023-10-13 13:40:08 -05:00
static func missingScreenshotSize ( for screenshot : AppScreenshot , source : Source ) -> SourceError {
SourceError ( code : . missingScreenshotSize , source : source , app : screenshot . app , screenshotURL : screenshot . imageURL )
}
2024-02-15 15:34:28 -06:00
2024-02-15 18:17:51 -06:00
static func marketplaceNotSupported ( source : Source ) -> SourceError {
return SourceError ( code : . marketplaceNotSupported , source : source )
2024-02-15 15:34:28 -06:00
}
2024-02-15 18:17:51 -06:00
static func marketplaceRequired ( source : Source ) -> SourceError {
return SourceError ( code : . marketplaceRequired , source : source )
2024-02-15 15:34:28 -06:00
}
2023-05-15 15:38:54 -05:00
}
struct SourceError : ALTLocalizedError
{
let code : Code
var errorTitle : String ?
var errorFailure : String ?
@ Managed var source : Source
2023-05-16 15:39:38 -05:00
2023-05-15 15:38:54 -05:00
@ Managed var app : StoreApp ?
2023-05-16 15:39:38 -05:00
@ Managed var existingSource : Source ?
2023-05-15 15:38:54 -05:00
var version : String ?
2023-05-16 15:39:38 -05:00
var bundleID : String ?
var bundleIDs : [ String ] ?
2023-05-15 15:38:54 -05:00
// S t o r e i n u s e r I n f o s o t h e y c a n b e v i e w e d f r o m E r r o r L o g .
@ UserInfoValue var sourceID : String ?
@ UserInfoValue var previousSourceID : String ?
@ UserInfoValue
var permission : ( any ALTAppPermission ) ?
2023-10-13 13:40:08 -05:00
@ UserInfoValue
var screenshotURL : URL ?
2023-05-15 15:38:54 -05:00
var errorFailureReason : String {
switch self . code
{
2024-12-13 13:59:18 +05:30
case . unsupported : return String ( format : NSLocalizedString ( " The source “%@” is not supported by this version of SideStore. " , comment : " " ) , self . $ source . name )
2023-05-15 15:38:54 -05:00
case . duplicateBundleID :
let bundleIDFragment = self . bundleID . map { String ( format : NSLocalizedString ( " the bundle identifier %@ " , comment : " " ) , $0 ) } ? ? NSLocalizedString ( " the same bundle identifier " , comment : " " )
let failureReason = String ( format : NSLocalizedString ( " The source “%@” contains multiple apps with %@. " , comment : " " ) , self . $ source . name , bundleIDFragment )
return failureReason
case . duplicateVersion :
var versionFragment = NSLocalizedString ( " duplicate versions " , comment : " " )
if let version
{
versionFragment += " ( \( version ) ) "
}
let appFragment : String
if let name = self . $ app . name , let bundleID = self . $ app . bundleIdentifier
{
appFragment = name + " ( \( bundleID ) ) "
}
else
{
appFragment = NSLocalizedString ( " one or more apps " , comment : " " )
}
let failureReason = String ( format : NSLocalizedString ( " The source “%@” contains %@ for %@. " , comment : " " ) , self . $ source . name , versionFragment , appFragment )
return failureReason
2023-05-15 16:25:25 -05:00
case . blocked :
2024-12-13 13:59:18 +05:30
let failureReason = String ( format : NSLocalizedString ( " The source “%@” has been blocked by SideStore for security reasons. " , comment : " " ) , self . $ source . name )
2023-05-15 16:25:25 -05:00
return failureReason
2023-05-15 15:38:54 -05:00
case . changedID :
let failureReason = String ( format : NSLocalizedString ( " The identifier of the source “%@” has changed. " , comment : " " ) , self . $ source . name )
return failureReason
case . duplicate :
let baseMessage = String ( format : NSLocalizedString ( " A source with the identifier '%@' already exists " , comment : " " ) , self . $ source . identifier )
2023-05-16 15:39:38 -05:00
guard let existingSourceName = self . $ existingSource . name else { return baseMessage + " . " }
2023-05-15 15:38:54 -05:00
2023-05-16 15:39:38 -05:00
let failureReason = baseMessage + " (“ \( existingSourceName ) ”). "
2023-05-15 15:38:54 -05:00
return failureReason
case . missingPermissionUsageDescription :
let appName = self . $ app . name ? ? String ( format : NSLocalizedString ( " an app in source “%@” " , comment : " " ) , self . $ source . name )
guard let permission else {
return String ( format : NSLocalizedString ( " A permission for %@ is missing a usage description. " , comment : " " ) , appName )
}
let permissionType = permission . type . localizedName ? ? NSLocalizedString ( " Permission " , comment : " " )
let failureReason = String ( format : NSLocalizedString ( " The %@ '%@' for %@ is missing a usage description. " , comment : " " ) , permissionType . lowercased ( ) , permission . rawValue , appName )
return failureReason
2023-10-13 13:40:08 -05:00
case . missingScreenshotSize :
let appName = self . $ app . name ? ? String ( format : NSLocalizedString ( " an app in source “%@” " , comment : " " ) , self . $ source . name )
let baseMessage = String ( format : NSLocalizedString ( " An iPad screenshot for %@ does not specify its size " , comment : " " ) , appName )
guard let screenshotURL else { return baseMessage + " . " }
let failureReason = baseMessage + " : \( screenshotURL . absoluteString ) "
return failureReason
2024-02-15 15:34:28 -06:00
2024-02-15 18:17:51 -06:00
case . marketplaceNotSupported :
2024-12-13 13:59:18 +05:30
let failureReason = String ( format : NSLocalizedString ( " The source “%@” contains notarized apps, which are not supported by this version of SideStore. " , comment : " " ) , self . $ source . name )
2024-02-15 15:34:28 -06:00
return failureReason
2024-02-15 18:17:51 -06:00
case . marketplaceRequired :
2024-12-13 13:59:18 +05:30
let failureReason = String ( format : NSLocalizedString ( " One or more apps in source “%@” are missing a marketplaceID. This most likely means they are not notarized, which is not supported by this version of SideStore. " , comment : " " ) , self . $ source . name )
2024-02-15 15:34:28 -06:00
return failureReason
2023-05-15 15:38:54 -05:00
}
}
var recoverySuggestion : String ? {
switch self . code
{
2023-05-16 15:39:38 -05:00
case . blocked :
if self . existingSource != nil
{
// S o u r c e a l r e a d y a d d e d , s o t e l l t h e m t o r e m o v e i t + a n y i n s t a l l e d a p p s .
let baseMessage = NSLocalizedString ( " For your protection, please remove the source and uninstall " , comment : " " )
if let blockedAppNames = self . blockedAppNames
{
let recoverySuggestion = baseMessage + " " + NSLocalizedString ( " the following apps: " , comment : " " ) + " \n \n " + blockedAppNames . joined ( separator : " \n " )
return recoverySuggestion
}
else
{
let recoverySuggestion = baseMessage + " " + NSLocalizedString ( " all apps downloaded from it. " , comment : " " )
return recoverySuggestion
}
}
else
{
// S o u r c e i s n o t a l r e a d y a d d e d , s o n o n e e d t o t e l l u s e r s t o r e m o v e i t .
// I n s t e a d , w e j u s t l i s t a l l a f f e c t e d a p p s ( i f p r o v i d e d ) .
guard let blockedAppNames else { return nil }
let recoverySuggestion = NSLocalizedString ( " The following apps have been flagged: " , comment : " " ) + " \n \n " + blockedAppNames . joined ( separator : " \n " )
return recoverySuggestion
}
2023-05-15 15:38:54 -05:00
case . changedID : return NSLocalizedString ( " A source cannot change its identifier once added. This source can no longer be updated. " , comment : " " )
case . duplicate :
2023-05-16 15:39:38 -05:00
let recoverySuggestion = NSLocalizedString ( " Please remove the existing source in order to add this one. " , comment : " " )
return recoverySuggestion
2023-05-15 15:38:54 -05:00
2024-02-15 18:17:51 -06:00
case . marketplaceRequired :
2024-12-13 13:59:18 +05:30
let failureReason = String ( format : NSLocalizedString ( " SideStore can only install marketplace apps that have been notarized by Apple. " , comment : " " ) , self . $ source . name )
2024-02-15 15:34:28 -06:00
return failureReason
2023-05-15 15:38:54 -05:00
default : return nil
}
}
}
2023-05-16 15:39:38 -05:00
private extension SourceError
{
var blockedAppNames : [ String ] ? {
let blockedAppNames : [ String ] ?
if let existingSource
{
// B l o c k e d a p p s = a l l i n s t a l l e d a p p s f r o m t h i s s o u r c e .
blockedAppNames = self . $ existingSource . perform { _ in
let storeApps = existingSource . apps . lazy . filter { $0 . installedApp != nil }
guard ! storeApps . isEmpty else { return nil }
let appNames = storeApps . map { " \( $0 . name ) ( \( $0 . bundleIdentifier ) ) " }
return Array ( appNames )
}
}
else if let bundleIDs
{
// B l o c k e d a p p s = e x p l i c i t l y l i s t e d b u n d l e I D s i n b l o c k e d s o u r c e J S O N e n t r y .
blockedAppNames = self . $ source . perform { source in
bundleIDs . compactMap { ( bundleID ) in
guard let storeApp = source . _apps . lazy . compactMap ( { $0 as ? StoreApp } ) . first ( where : { $0 . bundleIdentifier = = bundleID } ) else { return nil }
return " \( storeApp . name ) ( \( storeApp . bundleIdentifier ) ) "
}
}
}
else
{
blockedAppNames = nil
}
let sortedNames = blockedAppNames ? . sorted { $0 . localizedCompare ( $1 ) = = . orderedAscending }
return sortedNames
}
}