mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Uses URL schemes to determine whether apps are installed
This commit is contained in:
@@ -13,5 +13,15 @@ public extension Bundle
|
|||||||
struct Info
|
struct Info
|
||||||
{
|
{
|
||||||
public static let deviceID = "ALTDeviceID"
|
public static let deviceID = "ALTDeviceID"
|
||||||
|
|
||||||
|
public static let urlTypes = "CFBundleURLTypes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension Bundle
|
||||||
|
{
|
||||||
|
var infoPlistURL: URL {
|
||||||
|
let infoPlistURL = self.bundleURL.appendingPathComponent("Info.plist")
|
||||||
|
return infoPlistURL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -355,11 +355,11 @@ private extension ViewController
|
|||||||
let zippedURL = try FileManager.default.zipAppBundle(at: appBundleURL)
|
let zippedURL = try FileManager.default.zipAppBundle(at: appBundleURL)
|
||||||
|
|
||||||
let resigner = ALTSigner(team: team, certificate: certificate)
|
let resigner = ALTSigner(team: team, certificate: certificate)
|
||||||
resigner.signApp(at: zippedURL, provisioningProfile: profile) { (resignedURL, error) in
|
resigner.signApp(at: zippedURL, provisioningProfile: profile) { (success, error) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
let resignedURL = try Result(resignedURL, error).get()
|
try Result(success, error).get()
|
||||||
ALTDeviceManager.shared.installApp(at: resignedURL, toDeviceWithUDID: device.identifier) { (success, error) in
|
ALTDeviceManager.shared.installApp(at: ipaURL, toDeviceWithUDID: device.identifier) { (success, error) in
|
||||||
let result = Result(success, error)
|
let result = Result(success, error)
|
||||||
print(result)
|
print(result)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
print("Started DatabaseManager")
|
print("Started DatabaseManager")
|
||||||
|
|
||||||
|
AppManager.shared.refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +48,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
|
|
||||||
func applicationWillEnterForeground(_ application: UIApplication)
|
func applicationWillEnterForeground(_ application: UIApplication)
|
||||||
{
|
{
|
||||||
|
AppManager.shared.refresh()
|
||||||
ServerManager.shared.startDiscovering()
|
ServerManager.shared.startDiscovering()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ extension AppManager
|
|||||||
case download(URLError)
|
case download(URLError)
|
||||||
case authentication(Error)
|
case authentication(Error)
|
||||||
case fetchingSigningResources(Error)
|
case fetchingSigningResources(Error)
|
||||||
case sign(Error)
|
case prepare(Error)
|
||||||
case install(Error)
|
case install(Error)
|
||||||
|
|
||||||
var errorDescription: String? {
|
var errorDescription: String? {
|
||||||
@@ -41,7 +41,7 @@ extension AppManager
|
|||||||
case .download(let error): return error.localizedDescription
|
case .download(let error): return error.localizedDescription
|
||||||
case .authentication(let error): return error.localizedDescription
|
case .authentication(let error): return error.localizedDescription
|
||||||
case .fetchingSigningResources(let error): return error.localizedDescription
|
case .fetchingSigningResources(let error): return error.localizedDescription
|
||||||
case .sign(let error): return error.localizedDescription
|
case .prepare(let error): return error.localizedDescription
|
||||||
case .install(let error): return error.localizedDescription
|
case .install(let error): return error.localizedDescription
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,11 +59,44 @@ class AppManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension AppManager
|
||||||
|
{
|
||||||
|
func refresh()
|
||||||
|
{
|
||||||
|
let context = DatabaseManager.shared.persistentContainer.newBackgroundSavingViewContext()
|
||||||
|
|
||||||
|
let fetchRequest = InstalledApp.fetchRequest() as NSFetchRequest<InstalledApp>
|
||||||
|
fetchRequest.relationshipKeyPathsForPrefetching = [#keyPath(InstalledApp.app)]
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
let installedApps = try context.fetch(fetchRequest)
|
||||||
|
for app in installedApps
|
||||||
|
{
|
||||||
|
if UIApplication.shared.canOpenURL(app.openAppURL)
|
||||||
|
{
|
||||||
|
// App is still installed, good!
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.delete(app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try context.save()
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
print("Error while fetching installed apps")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension AppManager
|
extension AppManager
|
||||||
{
|
{
|
||||||
func install(_ app: App, presentingViewController: UIViewController, completionHandler: @escaping (Result<InstalledApp, AppError>) -> Void)
|
func install(_ app: App, presentingViewController: UIViewController, completionHandler: @escaping (Result<InstalledApp, AppError>) -> Void)
|
||||||
{
|
{
|
||||||
let ipaURL = app.ipaURL
|
let ipaURL = InstalledApp.ipaURL(for: app)
|
||||||
|
|
||||||
let backgroundTaskID = RSTBeginBackgroundTask("com.rileytestut.AltStore.InstallApp")
|
let backgroundTaskID = RSTBeginBackgroundTask("com.rileytestut.AltStore.InstallApp")
|
||||||
|
|
||||||
@@ -86,7 +119,6 @@ extension AppManager
|
|||||||
{
|
{
|
||||||
case .failure(let error): finish(.failure(.download(error)))
|
case .failure(let error): finish(.failure(.download(error)))
|
||||||
case .success:
|
case .success:
|
||||||
|
|
||||||
// Authenticate
|
// Authenticate
|
||||||
self.authenticate(presentingViewController: presentingViewController) { (result) in
|
self.authenticate(presentingViewController: presentingViewController) { (result) in
|
||||||
switch result
|
switch result
|
||||||
@@ -101,31 +133,30 @@ extension AppManager
|
|||||||
case .failure(let error): finish(.failure(.fetchingSigningResources(error)))
|
case .failure(let error): finish(.failure(.fetchingSigningResources(error)))
|
||||||
case .success(let certificate, let profile):
|
case .success(let certificate, let profile):
|
||||||
|
|
||||||
// Sign app
|
// Prepare app
|
||||||
app.managedObjectContext?.perform {
|
|
||||||
self.sign(app, team: team, certificate: certificate, provisioningProfile: profile) { (result) in
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .failure(let error): finish(.failure(.sign(error)))
|
|
||||||
case .success(let resignedURL):
|
|
||||||
|
|
||||||
// Send app to server
|
|
||||||
app.managedObjectContext?.perform {
|
|
||||||
self.sendAppToServer(fileURL: resignedURL, identifier: app.identifier) { (result) in
|
|
||||||
switch result
|
|
||||||
{
|
|
||||||
case .failure(let error): finish(.failure(.install(error)))
|
|
||||||
case .success:
|
|
||||||
|
|
||||||
// Update database
|
|
||||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||||
let app = context.object(with: app.objectID) as! App
|
let app = context.object(with: app.objectID) as! App
|
||||||
|
|
||||||
let installedApp = InstalledApp(app: app,
|
let installedApp = InstalledApp(app: app,
|
||||||
bundleIdentifier: app.identifier,
|
bundleIdentifier: profile.appID.bundleIdentifier,
|
||||||
signedDate: Date(),
|
signedDate: Date(),
|
||||||
expirationDate: Date().addingTimeInterval(60 * 60 * 24 * 7),
|
expirationDate: Date().addingTimeInterval(60 * 60 * 24 * 7),
|
||||||
context: context)
|
context: context)
|
||||||
|
|
||||||
|
self.prepare(installedApp, team: team, certificate: certificate, provisioningProfile: profile) { (result) in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(let error): finish(.failure(.prepare(error)))
|
||||||
|
case .success(let resignedURL):
|
||||||
|
|
||||||
|
// Send app to server
|
||||||
|
context.perform {
|
||||||
|
self.sendAppToServer(fileURL: resignedURL, identifier: installedApp.bundleIdentifier) { (result) in
|
||||||
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(let error): finish(.failure(.install(error)))
|
||||||
|
case .success:
|
||||||
|
context.perform {
|
||||||
finish(.success(installedApp))
|
finish(.success(installedApp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,12 +286,52 @@ private extension AppManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sign(_ app: App, team: ALTTeam, certificate: ALTCertificate, provisioningProfile: ALTProvisioningProfile, completionHandler: @escaping (Result<URL, Error>) -> Void)
|
func prepare(_ installedApp: InstalledApp, team altTeam: ALTTeam, certificate: ALTCertificate, provisioningProfile: ALTProvisioningProfile, completionHandler: @escaping (Result<URL, Error>) -> Void)
|
||||||
{
|
{
|
||||||
let signer = ALTSigner(team: team, certificate: certificate)
|
do
|
||||||
signer.signApp(at: app.ipaURL, provisioningProfile: provisioningProfile) { (resignedURL, error) in
|
{
|
||||||
let result = Result(resignedURL, error)
|
let refreshedAppDirectory = installedApp.directoryURL.appendingPathComponent("Refreshed", isDirectory: true)
|
||||||
completionHandler(result)
|
|
||||||
|
if FileManager.default.fileExists(atPath: refreshedAppDirectory.path)
|
||||||
|
{
|
||||||
|
try FileManager.default.removeItem(at: refreshedAppDirectory)
|
||||||
|
}
|
||||||
|
try FileManager.default.createDirectory(at: refreshedAppDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||||
|
|
||||||
|
let appBundleURL = try FileManager.default.unzipAppBundle(at: installedApp.ipaURL, toDirectory: refreshedAppDirectory)
|
||||||
|
guard let bundle = Bundle(url: appBundleURL) else { throw ALTError(.missingAppBundle) }
|
||||||
|
|
||||||
|
guard var infoDictionary = NSDictionary(contentsOf: bundle.infoPlistURL) as? [String: Any] else { throw ALTError(.missingInfoPlist) }
|
||||||
|
|
||||||
|
var allURLSchemes = infoDictionary[Bundle.Info.urlTypes] as? [[String: Any]] ?? []
|
||||||
|
|
||||||
|
let altstoreURLScheme = ["CFBundleTypeRole": "Editor",
|
||||||
|
"CFBundleURLName": installedApp.bundleIdentifier,
|
||||||
|
"CFBundleURLSchemes": [installedApp.openAppURL.scheme!]] as [String : Any]
|
||||||
|
allURLSchemes.append(altstoreURLScheme)
|
||||||
|
|
||||||
|
infoDictionary[Bundle.Info.urlTypes] = allURLSchemes
|
||||||
|
|
||||||
|
try (infoDictionary as NSDictionary).write(to: bundle.infoPlistURL)
|
||||||
|
|
||||||
|
let signer = ALTSigner(team: altTeam, certificate: certificate)
|
||||||
|
signer.signApp(at: appBundleURL, provisioningProfile: provisioningProfile) { (success, error) in
|
||||||
|
do
|
||||||
|
{
|
||||||
|
try Result(success, error).get()
|
||||||
|
|
||||||
|
let resignedURL = try FileManager.default.zipAppBundle(at: appBundleURL)
|
||||||
|
completionHandler(.success(resignedURL))
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
completionHandler(.failure(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
completionHandler(.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>ALTDeviceID</key>
|
||||||
|
<string>1c3416b7b0ab68773e6e7eb7f0d110f7c9353acc</string>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
@@ -18,6 +20,10 @@
|
|||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>altstore-com.rileytestut.Delta</string>
|
||||||
|
</array>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
@@ -51,7 +57,5 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>ALTDeviceID</key>
|
|
||||||
<string>1c3416b7b0ab68773e6e7eb7f0d110f7c9353acc</string>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -84,29 +84,3 @@ extension App
|
|||||||
return NSFetchRequest<App>(entityName: "App")
|
return NSFetchRequest<App>(entityName: "App")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension App
|
|
||||||
{
|
|
||||||
class var appsDirectoryURL: URL {
|
|
||||||
let appsDirectoryURL = FileManager.default.applicationSupportDirectory.appendingPathComponent("Apps")
|
|
||||||
|
|
||||||
do { try FileManager.default.createDirectory(at: appsDirectoryURL, withIntermediateDirectories: true, attributes: nil) }
|
|
||||||
catch { print(error) }
|
|
||||||
|
|
||||||
return appsDirectoryURL
|
|
||||||
}
|
|
||||||
|
|
||||||
var directoryURL: URL {
|
|
||||||
let directoryURL = App.appsDirectoryURL.appendingPathComponent(self.identifier)
|
|
||||||
|
|
||||||
do { try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil) }
|
|
||||||
catch { print(error) }
|
|
||||||
|
|
||||||
return directoryURL
|
|
||||||
}
|
|
||||||
|
|
||||||
var ipaURL: URL {
|
|
||||||
let ipaURL = self.directoryURL.appendingPathComponent("App.ipa")
|
|
||||||
return ipaURL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class InstalledApp: NSManagedObject
|
|||||||
@NSManaged var isBeta: Bool
|
@NSManaged var isBeta: Bool
|
||||||
|
|
||||||
/* Relationships */
|
/* Relationships */
|
||||||
@NSManaged private(set) var app: App?
|
@NSManaged private(set) var app: App!
|
||||||
|
|
||||||
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
private override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?)
|
||||||
{
|
{
|
||||||
@@ -50,3 +50,48 @@ extension InstalledApp
|
|||||||
return NSFetchRequest<InstalledApp>(entityName: "InstalledApp")
|
return NSFetchRequest<InstalledApp>(entityName: "InstalledApp")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension InstalledApp
|
||||||
|
{
|
||||||
|
var openAppURL: URL {
|
||||||
|
// Don't use the actual bundle ID yet since we're hardcoding support for the first apps in AltStore.
|
||||||
|
let openAppURL = URL(string: "altstore-" + self.app.identifier + "://")!
|
||||||
|
return openAppURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension InstalledApp
|
||||||
|
{
|
||||||
|
class var appsDirectoryURL: URL {
|
||||||
|
let appsDirectoryURL = FileManager.default.applicationSupportDirectory.appendingPathComponent("Apps")
|
||||||
|
|
||||||
|
do { try FileManager.default.createDirectory(at: appsDirectoryURL, withIntermediateDirectories: true, attributes: nil) }
|
||||||
|
catch { print(error) }
|
||||||
|
|
||||||
|
return appsDirectoryURL
|
||||||
|
}
|
||||||
|
|
||||||
|
class func ipaURL(for app: App) -> URL
|
||||||
|
{
|
||||||
|
let ipaURL = self.directoryURL(for: app).appendingPathComponent("App.ipa")
|
||||||
|
return ipaURL
|
||||||
|
}
|
||||||
|
|
||||||
|
class func directoryURL(for app: App) -> URL
|
||||||
|
{
|
||||||
|
let directoryURL = InstalledApp.appsDirectoryURL.appendingPathComponent(app.identifier)
|
||||||
|
|
||||||
|
do { try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil) }
|
||||||
|
catch { print(error) }
|
||||||
|
|
||||||
|
return directoryURL
|
||||||
|
}
|
||||||
|
|
||||||
|
var directoryURL: URL {
|
||||||
|
return InstalledApp.directoryURL(for: self.app)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ipaURL: URL {
|
||||||
|
return InstalledApp.ipaURL(for: self.app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
2
Dependencies/AltSign
vendored
2
Dependencies/AltSign
vendored
Submodule Dependencies/AltSign updated: 8ca7fa3f19...e3cee11a3a
Reference in New Issue
Block a user