2025-01-13 02:30:38 +05:30
//
// R e f r e s h A p p O p e r a t i o n . s w i f t
// A l t S t 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 2 / 2 7 / 2 0 .
// C o p y r i g h t © 2 0 2 0 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 Foundation
import AltStoreCore
import Roxas
import AltSign
@objc ( RemoveAppExtensionsOperation )
final class RemoveAppExtensionsOperation : ResultOperation < Void >
{
let context : AppOperationContext
2025-01-13 09:33:20 +05:30
let localAppExtensions : Set < ALTApplication > ?
2025-01-13 02:30:38 +05:30
2025-01-13 09:33:20 +05:30
init ( context : AppOperationContext , localAppExtensions : Set < ALTApplication > ? )
2025-01-13 02:30:38 +05:30
{
self . context = context
2025-01-13 09:33:20 +05:30
self . localAppExtensions = localAppExtensions
2025-01-13 02:30:38 +05:30
super . init ( )
}
override func main ( )
{
super . main ( )
if let error = self . context . error
{
self . finish ( . failure ( error ) )
return
}
guard let targetAppBundle = context . app else {
return self . finish ( . failure (
OperationError . invalidParameters ( " RemoveAppExtensionsOperation: context.app is nil " )
) )
}
self . removeAppExtensions ( from : targetAppBundle ,
2025-01-13 09:33:20 +05:30
localAppExtensions : localAppExtensions ,
2025-01-13 02:30:38 +05:30
extensions : targetAppBundle . appExtensions ,
context . authenticatedContext . presentingViewController )
}
private static func removeExtensions ( from extensions : Set < ALTApplication > ) throws {
for appExtension in extensions {
print ( " Deleting extension \( appExtension . bundleIdentifier ) " )
try FileManager . default . removeItem ( at : appExtension . fileURL )
}
}
2025-02-08 04:45:22 +05:30
private func updateManifest ( ) throws {
guard let app = context . app else {
return
}
let scInfoURL = app . fileURL . appendingPathComponent ( " SC_Info " )
let manifestPlistURL = scInfoURL . appendingPathComponent ( " Manifest.plist " )
if let manifestPlist = NSMutableDictionary ( contentsOf : manifestPlistURL ) ,
let sinfReplicationPaths = manifestPlist [ " SinfReplicationPaths " ] as ? [ String ]
{
let replacementPaths = sinfReplicationPaths . filter { ! $0 . starts ( with : " PlugIns/ " ) } // F i l t e r o u t a p p e x t e n s i o n p a t h s .
manifestPlist [ " SinfReplicationPaths " ] = replacementPaths
try manifestPlist . write ( to : manifestPlistURL )
}
}
2025-01-13 02:30:38 +05:30
private func removeAppExtensions ( from targetAppBundle : ALTApplication ,
2025-01-13 09:33:20 +05:30
localAppExtensions : Set < ALTApplication > ? ,
2025-01-13 07:20:44 +05:30
extensions : Set < ALTApplication > ,
_ presentingViewController : UIViewController ? )
2025-01-13 02:30:38 +05:30
{
// t a r g e t A p p B u n d l e d o e s n ' t c o n t a i n e x t e n s i o n s s o d o n ' t b o t h e r
guard ! targetAppBundle . appExtensions . isEmpty else {
return self . finish ( . success ( ( ) ) )
}
2025-01-13 07:20:44 +05:30
// p r o c e s s e x t e n s i o n s I n f o
2025-01-13 09:33:20 +05:30
let excessExtensions = processExtensionsInfo ( from : targetAppBundle , localAppExtensions : localAppExtensions )
2025-01-13 07:20:44 +05:30
DispatchQueue . main . async {
guard let presentingViewController : UIViewController = presentingViewController ,
presentingViewController . viewIfLoaded ? . window != nil else {
// b a c k g r o u n d m o d e : r e m o v e o n l y t h e e x c e s s e x t e n s i o n s a u t o m a t i c a l l y f o r r e - i n s t a l l s
2025-01-13 09:33:20 +05:30
// k e e p a l l e x t e n s i o n s f o r f r e s h i n s t a l l ( l o c a l A p p B u n d l e = n i l )
2025-01-13 07:20:44 +05:30
return self . backgroundModeExtensionsCleanup ( excessExtensions : excessExtensions )
2025-01-13 02:30:38 +05:30
}
2025-01-13 07:20:44 +05:30
// p r e s e n t p r o m p t t o t h e u s e r i f w e h a v e a v i e w c o n t e x t
let alertController = self . createAlertDialog ( from : targetAppBundle , extensions : extensions , presentingViewController )
presentingViewController . present ( alertController , animated : true ) {
// i f f o r a n y r e a s o n t h e v i e w w a s n ' t p r e s e n t e d , t h e n j u s t s i g n a l t h a t a s e r r o r
2025-01-29 02:03:17 +05:30
if presentingViewController . presentedViewController = = nil && ! alertController . isViewLoaded {
2025-01-13 07:20:44 +05:30
let errMsg = " RemoveAppExtensionsOperation: unable to present dialog, view context not available. " +
" \n Did you move to different screen or background after starting the operation? "
self . finish ( . failure (
OperationError . invalidOperationContext ( errMsg )
) )
}
}
}
}
private func createAlertDialog ( from targetAppBundle : ALTApplication ,
extensions : Set < ALTApplication > ,
_ presentingViewController : UIViewController ) -> UIAlertController
{
2025-01-13 02:30:38 +05:30
2025-01-13 07:20:44 +05:30
// / F o r e g r o u n d p r o m p t :
2025-01-13 02:30:38 +05:30
let firstSentence : String
if UserDefaults . standard . activeAppLimitIncludesExtensions
{
firstSentence = NSLocalizedString ( " Non-developer Apple IDs are limited to 3 active apps and app extensions. " , comment : " " )
}
else
{
firstSentence = NSLocalizedString ( " Non-developer Apple IDs are limited to creating 10 App IDs per week. " , comment : " " )
}
let message = firstSentence + " " + NSLocalizedString ( " Would you like to remove this app's extensions so they don't count towards your limit? There are \( extensions . count ) Extensions " , comment : " " )
let alertController = UIAlertController ( title : NSLocalizedString ( " App Contains Extensions " , comment : " " ) , message : message , preferredStyle : . alert )
alertController . addAction ( UIAlertAction ( title : UIAlertAction . cancel . title , style : UIAlertAction . cancel . style , handler : { ( action ) in
self . finish ( . failure ( OperationError . cancelled ) )
} ) )
2025-06-22 16:13:15 +08:00
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Keep App Extensions (Use Main Profile) " , comment : " " ) , style : . default ) { ( action ) in
self . context . useMainProfile = true
self . finish ( . success ( ( ) ) )
} )
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Keep App Extensions (Register App ID for Each Extension) " , comment : " " ) , style : . default ) { ( action ) in
2025-01-13 02:30:38 +05:30
self . finish ( . success ( ( ) ) )
} )
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Remove App Extensions " , comment : " " ) , style : . destructive ) { ( action ) in
do {
try Self . removeExtensions ( from : targetAppBundle . appExtensions )
2025-02-08 04:45:22 +05:30
try self . updateManifest ( )
2025-01-13 02:30:38 +05:30
return self . finish ( . success ( ( ) ) )
} catch {
return self . finish ( . failure ( error ) )
}
} )
alertController . addAction ( UIAlertAction ( title : NSLocalizedString ( " Choose App Extensions " , comment : " " ) , style : . default ) { ( action ) in
let popoverContentController = AppExtensionViewHostingController ( extensions : extensions ) { ( selection ) in
do {
try Self . removeExtensions ( from : Set ( selection ) )
return self . finish ( . success ( ( ) ) )
} catch {
return self . finish ( . failure ( error ) )
}
}
let suiview = popoverContentController . view !
suiview . translatesAutoresizingMaskIntoConstraints = false
popoverContentController . modalPresentationStyle = . popover
if let popoverPresentationController = popoverContentController . popoverPresentationController {
popoverPresentationController . sourceView = presentingViewController . view
popoverPresentationController . sourceRect = CGRect ( x : 50 , y : 50 , width : 4 , height : 4 )
popoverPresentationController . delegate = popoverContentController
DispatchQueue . main . async {
presentingViewController . present ( popoverContentController , animated : true )
}
} else {
self . finish ( . failure (
OperationError . invalidParameters ( " RemoveAppExtensionsOperation: popoverContentController.popoverPresentationController is nil " ) )
)
}
} )
2025-01-13 07:20:44 +05:30
return alertController
}
struct ExtensionsInfo {
let excessInTarget : Set < ALTApplication >
let necessaryInExisting : Set < ALTApplication >
}
private func processExtensionsInfo ( from targetAppBundle : ALTApplication ,
2025-01-13 09:33:20 +05:30
localAppExtensions : Set < ALTApplication > ? ) -> Set < ALTApplication >
2025-01-13 07:20:44 +05:30
{
// A p p - E x t e n s i o n s : E n s u r e e x i s t i n g a p p ' s e x t e n s i o n s i n D B a n d c u r r e n t l y i n s t a l l i n g a p p b u n d l e ' s e x t e n s i o n s m u s t m a t c h
let targetAppEx : Set < ALTApplication > = targetAppBundle . appExtensions
let targetAppExNames = targetAppEx . map { appEx in appEx . bundleIdentifier }
2025-01-13 09:33:20 +05:30
guard let extensionsInExistingApp = localAppExtensions else {
2025-01-13 07:20:44 +05:30
let diagnosticsMsg = " RemoveAppExtensionsOperation: ExistingApp is nil, Hence keeping all app extensions from targetAppBundle "
+ " RemoveAppExtensionsOperation: ExistingAppEx: nil; targetAppBundleEx: \( targetAppExNames ) "
print ( diagnosticsMsg )
return Set ( ) // n o t h i n g i s e x c e s s s i n c e w e a r e k e e p i n g a l l , s o r e t u r n i n g e m p t y
}
let existingAppEx : Set < ALTApplication > = extensionsInExistingApp
let existingAppExNames = existingAppEx . map { appEx in appEx . bundleIdentifier }
let excessExtensionsInTargetApp = targetAppEx . filter {
! ( existingAppExNames . contains ( $0 . bundleIdentifier ) )
}
let isMatching = ( targetAppEx . count = = existingAppEx . count ) && excessExtensionsInTargetApp . isEmpty
2025-01-13 09:33:20 +05:30
let diagnosticsMsg = " RemoveAppExtensionsOperation: App Extensions in localAppBundle and targetAppBundle are matching: \( isMatching ) \n "
+ " RemoveAppExtensionsOperation: \n localAppBundleEx: \( existingAppExNames ) ; \n targetAppBundleEx: \( String ( describing : targetAppExNames ) ) \n "
2025-01-13 07:20:44 +05:30
print ( diagnosticsMsg )
return excessExtensionsInTargetApp
}
private func backgroundModeExtensionsCleanup ( excessExtensions : Set < ALTApplication > ) {
// p e r f o r m s i l e n t e x t e n s i o n s c l e a n u p f o r t h o s e t h a t a r e n ' t a l r e a d y p r e s e n t i n e x i s t i n g a p p
print ( " \n Performing background mode Extensions removal \n " )
print ( " RemoveAppExtensionsOperation: Excess Extensions In TargetAppBundle: \( excessExtensions . map { $0 . bundleIdentifier } ) " )
do {
try Self . removeExtensions ( from : excessExtensions )
return self . finish ( . success ( ( ) ) )
} catch {
return self . finish ( . failure ( error ) )
2025-01-13 02:30:38 +05:30
}
}
}