2020-03-06 17:08:35 -08:00
//
// F e t c h P r o v i s i o n i n g P r o f i l e s 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
2020-09-03 16:39:08 -07:00
import AltStoreCore
2020-03-06 17:08:35 -08:00
import AltSign
2020-09-03 16:39:08 -07:00
import Roxas
2020-03-06 17:08:35 -08:00
@objc ( FetchProvisioningProfilesOperation )
2025-01-19 08:06:41 +05:30
class FetchProvisioningProfilesOperation : ResultOperation < [ String : ALTProvisioningProfile ] >
2020-03-06 17:08:35 -08:00
{
let context : AppOperationContext
2020-05-15 14:54:43 -07:00
var additionalEntitlements : [ ALTEntitlement : Any ] ?
2025-01-19 08:06:41 +05:30
internal let appGroupsLock = NSLock ( )
2020-03-19 11:56:28 -07:00
2025-01-19 08:06:41 +05:30
// t h i s c l a s s i s a b s t r a c t o r s h o u l d n ' t b e i n s t a n t i a t e d o u t s i d e , u s e t h e s u b c l a s s e s
fileprivate init ( context : AppOperationContext )
2020-03-06 17:08:35 -08:00
{
self . context = context
super . init ( )
self . progress . totalUnitCount = 1
}
override func main ( )
{
super . main ( )
if let error = self . context . error
{
self . finish ( . failure ( error ) )
return
}
2025-01-19 08:06:41 +05:30
guard let team = self . context . team ,
let session = self . context . session else {
return self . finish ( . failure (
OperationError . invalidParameters ( " FetchProvisioningProfilesOperation.main: self.context.team or self.context.session is nil " ) )
)
}
2020-03-06 17:08:35 -08:00
2024-08-06 10:43:52 +09:00
guard let app = self . context . app else { return self . finish ( . failure ( OperationError . appNotFound ( name : nil ) ) ) }
2023-10-18 14:06:10 -05:00
Logger . sideload . notice ( " Fetching provisioning profiles for app \( self . context . bundleIdentifier , privacy : . public ) ... " )
2020-03-06 17:08:35 -08:00
self . progress . totalUnitCount = Int64 ( 1 + app . appExtensions . count )
self . prepareProvisioningProfile ( for : app , parentApp : nil , team : team , session : session ) { ( result ) in
do
{
self . progress . completedUnitCount += 1
let profile = try result . get ( )
var profiles = [ app . bundleIdentifier : profile ]
var error : Error ?
let dispatchGroup = DispatchGroup ( )
2025-06-22 16:13:15 +08:00
if ! self . context . useMainProfile {
for appExtension in app . appExtensions
{
dispatchGroup . enter ( )
2020-03-06 17:08:35 -08:00
2025-06-22 16:13:15 +08:00
self . prepareProvisioningProfile ( for : appExtension , parentApp : app , team : team , session : session ) { ( result ) in
switch result
{
case . failure ( let e ) : error = e
case . success ( let profile ) : profiles [ appExtension . bundleIdentifier ] = profile
}
dispatchGroup . leave ( )
self . progress . completedUnitCount += 1
}
2020-03-06 17:08:35 -08:00
}
}
dispatchGroup . notify ( queue : . global ( ) ) {
if let error = error
{
self . finish ( . failure ( error ) )
}
else
{
self . finish ( . success ( profiles ) )
}
}
}
catch
{
self . finish ( . failure ( error ) )
}
}
}
func process < T > ( _ result : Result < T , Error > ) -> T ?
{
switch result
{
case . failure ( let error ) :
self . finish ( . failure ( error ) )
return nil
case . success ( let value ) :
guard ! self . isCancelled else {
self . finish ( . failure ( OperationError . cancelled ) )
return nil
}
return value
}
}
2025-01-22 04:38:46 +05:30
internal func fetchProvisioningProfile ( for appID : ALTAppID , app : ALTApplication , team : ALTTeam , session : ALTAppleAPISession , completionHandler : @ escaping ( Result < ALTProvisioningProfile , Error > ) -> Void )
{
ALTAppleAPI . shared . fetchProvisioningProfile ( for : appID , deviceType : . iphone , team : team , session : session ) { ( profile , error ) in
switch Result ( profile , error )
{
case . failure ( let error ) : completionHandler ( . failure ( error ) )
case . success ( let profile ) :
// D e l e t e e x i s t i n g p r o f i l e
ALTAppleAPI . shared . delete ( profile , for : team , session : session ) { ( success , error ) in
switch Result ( success , error )
{
case . failure :
// A s o f M a r c h 2 0 , 2 0 2 3 , t h e f r e e p r o v i s i o n i n g p r o f i l e i s r e - g e n e r a t e d e a c h f e t c h , a n d y o u c a n n o l o n g e r d e l e t e i t .
// S o i n s t e a d , w e j u s t r e t u r n t h e f e t c h e d p r o f i l e f r o m a b o v e .
completionHandler ( . success ( profile ) )
case . success :
Logger . sideload . notice ( " Generating new free provisioning profile for App ID \( appID . bundleIdentifier , privacy : . public ) . " )
// F e t c h n e w p r o v i s i o n i n g p r o f i l e
ALTAppleAPI . shared . fetchProvisioningProfile ( for : appID , deviceType : . iphone , team : team , session : session ) { ( profile , error ) in
completionHandler ( Result ( profile , error ) )
}
}
}
}
}
}
2020-03-06 17:08:35 -08:00
}
extension FetchProvisioningProfilesOperation
{
2025-01-19 08:06:41 +05:30
private func prepareProvisioningProfile ( for app : ALTApplication ,
parentApp : ALTApplication ? ,
team : ALTTeam ,
session : ALTAppleAPISession , c
completionHandler : @ escaping ( Result < ALTProvisioningProfile , Error > ) -> Void )
2020-03-06 17:08:35 -08:00
{
DatabaseManager . shared . persistentContainer . performBackgroundTask { ( context ) in
2020-03-30 15:18:10 -07:00
let preferredBundleID : String ?
2020-03-06 17:08:35 -08:00
// C h e c k i f w e h a v e a l r e a d y i n s t a l l e d t h i s a p p w i t h t h i s t e a m b e f o r e .
2020-03-30 15:18:10 -07:00
let predicate = NSPredicate ( format : " %K == %@ " , # keyPath ( InstalledApp . bundleIdentifier ) , app . bundleIdentifier )
2020-03-06 17:08:35 -08:00
if let installedApp = InstalledApp . first ( satisfying : predicate , in : context )
{
2020-03-31 14:33:13 -07:00
// T e a m s m a t c h i f i n s t a l l e d A p p . t e a m h a s s a m e i d e n t i f i e r a s t e a m ,
// o r i f i n s t a l l e d A p p . t e a m i s n i l b u t r e s i g n e d B u n d l e I d e n t i f i e r c o n t a i n s t h e t e a m ' s i d e n t i f i e r .
let teamsMatch = installedApp . team ? . identifier = = team . identifier || ( installedApp . team = = nil && installedApp . resignedBundleIdentifier . contains ( team . identifier ) )
2025-01-12 18:01:11 +05:30
// TODO: @ m a h e e 9 6 : T r y t o k e e p t h e d e b u g b u i l d a n d r e l e a s e b u i l d o p e r a t i o n s s i m i l a r , r e f a c t o r l a t e r w i t h p r o p e r r e a s o n i n g
// f o r n o w , r e s t r i c t e d i t t o d e b u g o n s i m u l a t o r o n l y
#if DEBUG && targetEnvironment ( simulator )
if app . isAltStoreApp
{
// U s e l e g a c y b u n d l e I D f o r m a t f o r A l t S t o r e .
preferredBundleID = teamsMatch ? installedApp . resignedBundleIdentifier : nil
}
else
{
preferredBundleID = teamsMatch ? installedApp . resignedBundleIdentifier : nil
}
#else
2020-03-06 17:08:35 -08:00
2020-03-30 15:18:10 -07:00
if teamsMatch
{
// T h i s a p p i s a l r e a d y i n s t a l l e d w i t h t h e s a m e t e a m , s o u s e t h e s a m e r e s i g n e d b u n d l e i d e n t i f i e r a s b e f o r e .
// T h i s w a y , i f w e c h a n g e t h e i d e n t i f i e r f o r m a t ( a g a i n ) , A l t S t o r e w i l l c o n t i n u e t o u s e
// t h e o l d b u n d l e i d e n t i f i e r t o p r e v e n t i t f r o m i n s t a l l i n g a s a n e w a p p .
preferredBundleID = installedApp . resignedBundleIdentifier
}
else
{
preferredBundleID = nil
}
2020-03-06 17:08:35 -08:00
2025-01-12 18:01:11 +05:30
#endif
2020-03-06 17:08:35 -08:00
}
else
2020-03-30 15:18:10 -07:00
{
preferredBundleID = nil
}
let bundleID : String
if let preferredBundleID = preferredBundleID
{
bundleID = preferredBundleID
}
else
2020-03-06 17:08:35 -08:00
{
// T h i s a p p i s n ' t a l r e a d y i n s t a l l e d , s o c r e a t e t h e r e s i g n e d b u n d l e i d e n t i f i e r o u r s e l v e s .
// O r , i f t h e a p p _ i s _ i n s t a l l e d b u t w i t h a d i f f e r e n t t e a m , w e n e e d t o c r e a t e a n e w
// b u n d l e i d e n t i f i e r a n y w a y t o p r e v e n t c o l l i s i o n s w i t h t h e p r e v i o u s t e a m .
let parentBundleID = parentApp ? . bundleIdentifier ? ? app . bundleIdentifier
2020-07-20 14:57:13 -07:00
let updatedParentBundleID : String
2020-03-06 17:08:35 -08:00
2020-12-03 14:45:34 -06:00
if app . isAltStoreApp
2020-03-06 17:08:35 -08:00
{
2020-07-20 14:57:13 -07:00
// U s e l e g a c y b u n d l e I D f o r m a t f o r A l t S t o r e ( a n d i t s e x t e n s i o n s ) .
2022-11-17 18:16:21 -08:00
updatedParentBundleID = parentBundleID + " . " + team . identifier // A p p e n d j u s t t e a m i d e n t i f i e r t o m a k e i t h a r d e r t o t r a c k .
2020-03-06 17:08:35 -08:00
}
else
{
2020-07-20 14:57:13 -07:00
updatedParentBundleID = parentBundleID + " . " + team . identifier // A p p e n d j u s t t e a m i d e n t i f i e r t o m a k e i t h a r d e r t o t r a c k .
2020-03-06 17:08:35 -08:00
}
2020-07-20 14:57:13 -07:00
bundleID = app . bundleIdentifier . replacingOccurrences ( of : parentBundleID , with : updatedParentBundleID )
2020-03-06 17:08:35 -08:00
}
let preferredName : String
if let parentApp = parentApp
{
preferredName = parentApp . name + " " + app . name
}
else
{
preferredName = app . name
}
// R e g i s t e r
2020-03-30 15:18:10 -07:00
self . registerAppID ( for : app , name : preferredName , bundleIdentifier : bundleID , team : team , session : session ) { ( result ) in
2020-03-06 17:08:35 -08:00
switch result
{
case . failure ( let error ) : completionHandler ( . failure ( error ) )
case . success ( let appID ) :
2025-01-19 08:06:41 +05:30
// p r o c e s s
self . fetchProvisioningProfile (
2025-01-22 04:38:46 +05:30
for : appID , app : app , team : team , session : session , completionHandler : completionHandler
2025-01-19 08:06:41 +05:30
)
2020-03-06 17:08:35 -08:00
}
}
}
}
2025-01-19 08:06:41 +05:30
private func registerAppID ( for application : ALTApplication ,
name : String ,
bundleIdentifier : String ,
team : ALTTeam ,
session : ALTAppleAPISession ,
completionHandler : @ escaping ( Result < ALTAppID , Error > ) -> Void )
2020-03-06 17:08:35 -08:00
{
ALTAppleAPI . shared . fetchAppIDs ( for : team , session : session ) { ( appIDs , error ) in
do
{
let appIDs = try Result ( appIDs , error ) . get ( )
2020-05-07 12:45:09 -07:00
if let appID = appIDs . first ( where : { $0 . bundleIdentifier . lowercased ( ) = = bundleIdentifier . lowercased ( ) } )
2020-03-06 17:08:35 -08:00
{
2023-10-18 14:06:10 -05:00
Logger . sideload . notice ( " Using existing App ID \( appID . bundleIdentifier , privacy : . public ) " )
2020-03-06 17:08:35 -08:00
completionHandler ( . success ( appID ) )
}
else
{
let requiredAppIDs = 1 + application . appExtensions . count
let availableAppIDs = max ( 0 , Team . maximumFreeAppIDs - appIDs . count )
let sortedExpirationDates = appIDs . compactMap { $0 . expirationDate } . sorted ( by : { $0 < $1 } )
if team . type = = . free
{
if requiredAppIDs > availableAppIDs
{
if let expirationDate = sortedExpirationDates . first
{
2024-08-06 10:43:52 +09:00
throw OperationError . maximumAppIDLimitReached ( appName : application . name , requiredAppIDs : requiredAppIDs , availableAppIDs : availableAppIDs , expirationDate : expirationDate )
2020-03-06 17:08:35 -08:00
}
2024-11-10 02:54:18 +05:30
else
{
throw ALTAppleAPIError ( . maximumAppIDLimitReached )
}
2020-03-06 17:08:35 -08:00
}
}
2023-05-14 02:55:36 +08:00
// A p p I D n a m e m u s t b e a s c i i . I f t h e n a m e i s n o t a s c i i , u s i n g b u n d l e I D i n s t e a d
let appIDName : String
2023-05-14 19:06:22 +08:00
if ! name . allSatisfy ( { $0 . isASCII } ) {
2023-05-14 02:55:36 +08:00
// C o n t a i n s n o n A S C I I ( S u c h a s C h i n e s e / J a p a n e s e . . . ) , u s i n g b u n d l e I D
appIDName = bundleIdentifier
} else {
// A S C I I t e x t , k e e p g o i n g a s u s u a l
appIDName = name
}
2020-03-06 17:08:35 -08:00
2023-05-14 02:55:36 +08:00
ALTAppleAPI . shared . addAppID ( withName : appIDName , bundleIdentifier : bundleIdentifier , team : team , session : session ) { ( appID , error ) in
2020-03-06 17:08:35 -08:00
do
{
do
{
let appID = try Result ( appID , error ) . get ( )
2023-10-18 14:06:10 -05:00
Logger . sideload . notice ( " Registered new App ID \( appID . bundleIdentifier , privacy : . public ) " )
2020-03-06 17:08:35 -08:00
completionHandler ( . success ( appID ) )
}
catch ALTAppleAPIError . maximumAppIDLimitReached
{
if let expirationDate = sortedExpirationDates . first
{
2024-08-06 10:43:52 +09:00
throw OperationError . maximumAppIDLimitReached ( appName : application . name , requiredAppIDs : requiredAppIDs , availableAppIDs : availableAppIDs , expirationDate : expirationDate )
2020-03-06 17:08:35 -08:00
}
else
{
throw ALTAppleAPIError ( . maximumAppIDLimitReached )
}
}
}
2024-12-24 22:36:23 +09:00
catch ALTAppleAPIError . bundleIdentifierUnavailable {
ALTAppleAPI . shared . fetchAppIDs ( for : team , session : session ) { res , err in
if let err = err {
return completionHandler ( . failure ( err ) )
}
guard let res = res else { return completionHandler ( . failure ( ALTError ( . unknown ) ) ) }
for appid in res {
if appid . bundleIdentifier = = bundleIdentifier {
completionHandler ( . success ( appid ) )
}
}
}
}
2020-03-06 17:08:35 -08:00
catch
{
completionHandler ( . failure ( error ) )
}
}
}
}
catch
{
completionHandler ( . failure ( error ) )
}
}
}
2025-01-19 08:06:41 +05:30
}
class FetchProvisioningProfilesInstallOperation : FetchProvisioningProfilesOperation , @ unchecked Sendable {
override init ( context : AppOperationContext )
{
super . init ( context : context )
}
// m o d i f y O p e r a t i o n s a r e a l l o w e d f o r t h e a p p g r o u p s a n d o t h e r s t u f f s
2025-01-22 04:38:46 +05:30
override func fetchProvisioningProfile ( for appID : ALTAppID ,
app : ALTApplication ,
2025-01-19 08:06:41 +05:30
team : ALTTeam ,
session : ALTAppleAPISession ,
completionHandler : @ escaping ( Result < ALTProvisioningProfile , Error > ) -> Void )
{
// U p d a t e f e a t u r e s
self . updateFeatures ( for : appID , app : app , team : team , session : session ) { ( result ) in
switch result
{
case . failure ( let error ) : completionHandler ( . failure ( error ) )
case . success ( let appID ) :
// U p d a t e a p p g r o u p s
self . updateAppGroups ( for : appID , app : app , team : team , session : session ) { ( result ) in
switch result
{
case . failure ( let error ) : completionHandler ( . failure ( error ) )
case . success ( let appID ) :
// F e t c h P r o v i s i o n i n g P r o f i l e
2025-01-22 04:38:46 +05:30
super . fetchProvisioningProfile ( for : appID , app : app , team : team , session : session , completionHandler : completionHandler )
2025-01-19 08:06:41 +05:30
}
}
}
}
}
private func updateFeatures ( for appID : ALTAppID , app : ALTApplication , team : ALTTeam , session : ALTAppleAPISession , completionHandler : @ escaping ( Result < ALTAppID , Error > ) -> Void )
2020-03-06 17:08:35 -08:00
{
2020-05-15 14:54:43 -07:00
var entitlements = app . entitlements
for ( key , value ) in additionalEntitlements ? ? [ : ]
{
entitlements [ key ] = value
}
let requiredFeatures = entitlements . compactMap { ( entitlement , value ) -> ( ALTFeature , Any ) ? in
2020-03-06 17:08:35 -08:00
guard let feature = ALTFeature ( entitlement : entitlement ) else { return nil }
return ( feature , value )
}
var features = requiredFeatures . reduce ( into : [ ALTFeature : Any ] ( ) ) { $0 [ $1 . 0 ] = $1 . 1 }
2020-05-15 14:54:43 -07:00
if let applicationGroups = entitlements [ . appGroups ] as ? [ String ] , ! applicationGroups . isEmpty
2020-03-06 17:08:35 -08:00
{
2022-03-31 12:50:50 -07:00
// A p p u s e s a p p g r o u p s , s o a s s i g n ` t r u e ` t o e n a b l e t h e f e a t u r e .
2020-03-06 17:08:35 -08:00
features [ . appGroups ] = true
}
2022-03-31 12:50:50 -07:00
else
{
// A p p h a s n o a p p g r o u p s , s o a s s i g n ` f a l s e ` t o d i s a b l e t h e f e a t u r e .
features [ . appGroups ] = false
}
2020-03-06 17:08:35 -08:00
var updateFeatures = false
// D e t e r m i n e w h e t h e r t h e r e q u i r e d f e a t u r e s a r e a l r e a d y e n a b l e d f o r t h e A p p I D .
for ( feature , value ) in features
{
if let appIDValue = appID . features [ feature ] as AnyObject ? , ( value as AnyObject ) . isEqual ( appIDValue )
{
// A p p I D a l r e a d y h a s t h i s f e a t u r e e n a b l e d a n d t h e v a l u e s a r e t h e s a m e .
continue
}
2022-03-31 12:50:50 -07:00
else if appID . features [ feature ] = = nil , let shouldEnableFeature = value as ? Bool , ! shouldEnableFeature
{
// A p p I D d o e s n ' t a l r e a d y h a v e t h i s f e a t u r e e n a b l e d , b u t w e w a n t i t d i s a b l e d a n y w a y .
continue
}
2020-03-06 17:08:35 -08:00
else
{
// A p p I D e i t h e r d o e s n ' t h a v e t h i s f e a t u r e e n a b l e d o r t h e v a l u e h a s c h a n g e d ,
// s o w e n e e d t o u p d a t e i t t o r e f l e c t n e w v a l u e s .
updateFeatures = true
break
}
}
2024-12-24 16:30:14 +09:00
appID . entitlements = entitlements
2024-12-24 16:29:49 +09:00
if updateFeatures || true
2020-03-06 17:08:35 -08:00
{
let appID = appID . copy ( ) as ! ALTAppID
appID . features = features
2023-10-18 14:06:10 -05:00
ALTAppleAPI . shared . update ( appID , team : team , session : session ) { ( updatedAppID , error ) in
let result = Result ( updatedAppID , error )
switch result
{
case . success ( let appID ) : Logger . sideload . notice ( " Updated features for App ID \( appID . bundleIdentifier , privacy : . public ) . " )
case . failure ( let error ) : Logger . sideload . error ( " Failed to update features for App ID \( appID . bundleIdentifier , privacy : . public ) . \( error . localizedDescription , privacy : . public ) " )
}
completionHandler ( result )
2020-03-06 17:08:35 -08:00
}
}
else
{
completionHandler ( . success ( appID ) )
}
}
2025-01-19 08:06:41 +05:30
private func updateAppGroups ( for appID : ALTAppID , app : ALTApplication , team : ALTTeam , session : ALTAppleAPISession , completionHandler : @ escaping ( Result < ALTAppID , Error > ) -> Void )
2020-03-06 17:08:35 -08:00
{
2020-05-15 14:54:43 -07:00
var entitlements = app . entitlements
for ( key , value ) in additionalEntitlements ? ? [ : ]
{
entitlements [ key ] = value
}
2020-03-06 17:08:35 -08:00
2021-02-01 16:53:51 -06:00
guard var applicationGroups = entitlements [ . appGroups ] as ? [ String ] , ! applicationGroups . isEmpty else {
2023-10-18 14:06:10 -05:00
Logger . sideload . notice ( " App ID \( appID . bundleIdentifier , privacy : . public ) has no app groups, skipping assignment. " )
2021-02-01 16:53:51 -06:00
// A s s i g n i n g a n A p p I D t o a n e m p t y a p p g r o u p a r r a y f a i l s ,
// s o j u s t d o n o t h i n g i f t h e r e a r e n o a p p g r o u p s .
return completionHandler ( . success ( appID ) )
2020-05-16 11:42:31 -07:00
}
2020-03-06 17:08:35 -08:00
2020-12-07 16:13:10 -06:00
if app . isAltStoreApp
2020-05-18 16:00:08 -07:00
{
2023-04-02 07:34:48 -07:00
print ( " Application groups before modifying for SideStore: \( applicationGroups ) " )
2023-04-01 20:03:15 -07:00
// R e m o v e a p p g r o u p s t h a t c o n t a i n A l t S t o r e s i n c e t h e y c a n b e p r o b l e m a t i c ( c a u s e S i d e S t o r e t o e x p i r e e a r l y )
for ( index , group ) in applicationGroups . enumerated ( ) {
if group . contains ( " AltStore " ) {
print ( " Removing application group: \( group ) " )
applicationGroups . remove ( at : index )
}
}
2023-04-12 07:46:14 -07:00
// M a k e s u r e w e a d d . A l t W i d g e t f o r t h e w i d g e t
var altStoreAppGroupID = Bundle . baseAltStoreAppGroupID
for ( _ , group ) in applicationGroups . enumerated ( ) {
if group . contains ( " AltWidget " ) {
altStoreAppGroupID += " .AltWidget "
break
}
}
2020-12-07 16:13:10 -06:00
// P o t e n t i a l l y u p d a t i n g a p p g r o u p s f o r t h i s s p e c i f i c A l t S t o r e .
2020-05-18 16:00:08 -07:00
// F i n d t h e ( u n i q u e ) A l t S t o r e a p p g r o u p , t h e n r e p l a c e i t
// w i t h t h e c o r r e c t " b a s e " a p p g r o u p I D .
// O t h e r w i s e , w e m a y a p p e n d a d u p l i c a t e t e a m i d e n t i f i e r t o t h e e n d .
if let index = applicationGroups . firstIndex ( where : { $0 . contains ( Bundle . baseAltStoreAppGroupID ) } )
{
2023-04-12 07:46:14 -07:00
applicationGroups [ index ] = altStoreAppGroupID
2020-05-18 16:00:08 -07:00
}
else
{
2023-04-12 07:46:14 -07:00
applicationGroups . append ( altStoreAppGroupID )
2020-05-18 16:00:08 -07:00
}
}
2023-03-27 18:36:39 -07:00
print ( " Application groups: \( applicationGroups ) " )
2020-05-18 16:00:08 -07:00
2020-03-19 11:56:28 -07:00
// D i s p a t c h o n t o g l o b a l q u e u e t o p r e v e n t a p p G r o u p s L o c k d e a d l o c k .
DispatchQueue . global ( ) . async {
// E n s u r e w e ' r e n o t c o n c u r r e n t l y f e t c h i n g a n d u p d a t i n g a p p g r o u p s ,
// w h i c h c a n l e a d t o r a c e c o n d i t i o n s s u c h a s a d d i n g a n a p p g r o u p t w i c e .
self . appGroupsLock . lock ( )
2020-05-17 23:47:24 -07:00
func finish ( _ result : Result < ALTAppID , Error > )
{
self . appGroupsLock . unlock ( )
completionHandler ( result )
}
2020-03-19 11:56:28 -07:00
ALTAppleAPI . shared . fetchAppGroups ( for : team , session : session ) { ( groups , error ) in
switch Result ( groups , error )
2020-03-06 17:08:35 -08:00
{
2023-10-18 14:06:10 -05:00
case . failure ( let error ) :
Logger . sideload . error ( " Failed to fetch app groups for team \( team . identifier , privacy : . public ) . \( error . localizedDescription , privacy : . public ) " )
finish ( . failure ( error ) )
2020-05-17 23:47:24 -07:00
case . success ( let fetchedGroups ) :
let dispatchGroup = DispatchGroup ( )
2020-03-06 17:08:35 -08:00
2020-05-17 23:47:24 -07:00
var groups = [ ALTAppGroup ] ( )
var errors = [ Error ] ( )
for groupIdentifier in applicationGroups
2020-03-19 11:56:28 -07:00
{
2020-05-17 23:47:24 -07:00
let adjustedGroupIdentifier = groupIdentifier + " . " + team . identifier
2020-03-19 11:56:28 -07:00
2020-05-17 23:47:24 -07:00
if let group = fetchedGroups . first ( where : { $0 . groupIdentifier = = adjustedGroupIdentifier } )
{
groups . append ( group )
}
else
{
dispatchGroup . enter ( )
// N o t a l l c h a r a c t e r s a r e a l l o w e d i n g r o u p n a m e s , s o w e r e p l a c e p e r i o d s w i t h s p a c e s ( l i k e A p p l e d o e s ) .
let name = " AltStore " + groupIdentifier . replacingOccurrences ( of : " . " , with : " " )
ALTAppleAPI . shared . addAppGroup ( withName : name , groupIdentifier : adjustedGroupIdentifier , team : team , session : session ) { ( group , error ) in
switch Result ( group , error )
{
2023-10-18 14:06:10 -05:00
case . success ( let group ) :
Logger . sideload . notice ( " Created new App Group \( group . groupIdentifier , privacy : . public ) . " )
groups . append ( group )
2025-01-19 08:06:41 +05:30
case . failure ( let error ) :
2023-10-18 14:06:10 -05:00
Logger . sideload . notice ( " Failed to create new App Group \( adjustedGroupIdentifier , privacy : . public ) . \( error . localizedDescription , privacy : . public ) " )
errors . append ( error )
2020-05-17 23:47:24 -07:00
}
dispatchGroup . leave ( )
}
}
}
dispatchGroup . notify ( queue : . global ( ) ) {
if let error = errors . first
{
finish ( . failure ( error ) )
}
else
{
ALTAppleAPI . shared . assign ( appID , to : Array ( groups ) , team : team , session : session ) { ( success , error ) in
2023-10-18 14:06:10 -05:00
let groupIDs = groups . map { $0 . groupIdentifier }
switch Result ( success , error )
{
case . success :
Logger . sideload . notice ( " Assigned App ID \( appID . bundleIdentifier , privacy : . public ) to App Groups \( groupIDs . description , privacy : . public ) . " )
finish ( . success ( appID ) )
case . failure ( let error ) :
Logger . sideload . error ( " Failed to assign App ID \( appID . bundleIdentifier , privacy : . public ) to App Groups \( groupIDs . description , privacy : . public ) . \( error . localizedDescription , privacy : . public ) " )
finish ( . failure ( error ) )
}
2020-05-17 23:47:24 -07:00
}
2020-03-19 11:56:28 -07:00
}
2020-03-06 17:08:35 -08:00
}
}
}
}
}
}
2025-03-13 03:45:21 +05:30
// < T E S T > : u s e r s w e r e r e p o r t i n g t h a t r e f r e s h ( t h o u g h s e e m e d l i k e i t r e f r e s h e d t h e a p p b e c o m e s n o l o n g e r a v a i l a b l e )
// p o s s i b l y , t h i s i s c a u s e d s i n c e r e f e s h w a s n o t u p d a t i n g a p p F e a t u r e s a n d A p p G r o u p s i n t h e n e w p r o f i l e ? n o t s u r e .
// f o r n o w w e a r e r e v e r t i n g b y k e e p i n g s a m e o p e r a t i o n t h a t h a p p e n s d u r i n g f e t c h i n i n s t a l l p a t h t o s e e i f i t f i x e s i s s u e # 8 9 3
// c l a s s F e t c h P r o v i s i o n i n g P r o f i l e s R e f r e s h O p e r a t i o n : F e t c h P r o v i s i o n i n g P r o f i l e s O p e r a t i o n , @ u n c h e c k e d S e n d a b l e {
class FetchProvisioningProfilesRefreshOperation : FetchProvisioningProfilesInstallOperation , @ unchecked Sendable {
override init ( context : AppOperationContext )
{
super . init ( context : context )
}
}