xref: /expo/ios/Client/HomeAppLoaderTask.swift (revision 5f54863a)
1 // Copyright 2015-present 650 Industries. All rights reserved.
2 
3 import Foundation
4 import EXUpdates
5 
6 @objc(EXHomeAppLoaderTaskDelegate)
7 public protocol HomeAppLoaderTaskDelegate: AnyObject {
homeAppLoaderTasknull8   func homeAppLoaderTask(_: HomeAppLoaderTask, didFinishWithLauncher launcher: AppLauncher)
9   func homeAppLoaderTask(_: HomeAppLoaderTask, didFinishWithError error: Error)
10 }
11 
12 /**
13  Derivation of AppLoaderTask from expo-updates that just does a simple one-loader using HomeAppLoader.
14  */
15 @objc(EXHomeAppLoaderTask)
16 @objcMembers
17 public final class HomeAppLoaderTask: NSObject {
18   private static let ErrorDomain = "HomeAppLoaderTask"
19 
20   public weak var delegate: HomeAppLoaderTaskDelegate?
21 
22   private let manifestAndAssetRequestHeaders: ManifestAndAssetRequestHeaders
23   private let config: UpdatesConfig
24   private let database: UpdatesDatabase
25   private let directory: URL
26   private let selectionPolicy: SelectionPolicy
27   private let delegateQueue: DispatchQueue
28 
29   private let loaderTaskQueue: DispatchQueue
30 
31   public required init(
32     manifestAndAssetRequestHeaders: ManifestAndAssetRequestHeaders,
33     config: UpdatesConfig,
34     database: UpdatesDatabase,
35     directory: URL,
36     selectionPolicy: SelectionPolicy,
37     delegateQueue: DispatchQueue
38   ) {
39     self.manifestAndAssetRequestHeaders = manifestAndAssetRequestHeaders
40     self.config = config
41     self.database = database
42     self.directory = directory
43     self.selectionPolicy = selectionPolicy
44     self.delegateQueue = delegateQueue
45     self.loaderTaskQueue = DispatchQueue(label: "expo.loader.LoaderTaskQueue")
46   }
47 
48   public func start() {
49     self.loadHomeUpdate { remoteError, remoteUpdate in
50       self.loaderTaskQueue.async {
51         self.launchUpdate(remoteUpdate?.manifestUpdateResponsePart?.updateManifest, error: remoteError)
52       }
53     }
54   }
55 
56   private func finish(withLauncher launcher: AppLauncher?, error: Error?) {
57     dispatchPrecondition(condition: .onQueue(loaderTaskQueue))
58 
59     if let delegate = delegate {
60       delegateQueue.async {
61         // swiftlint:disable force_unwrapping
62         if let launcher = launcher,
63           launcher.launchAssetUrl != nil || launcher.launchedUpdate!.status == .StatusDevelopment {
64           delegate.homeAppLoaderTask(self, didFinishWithLauncher: launcher)
65         } else {
66           delegate.homeAppLoaderTask(
67             self,
68             didFinishWithError: error ?? NSError(
69               domain: HomeAppLoaderTask.ErrorDomain,
70               code: 1031,
71               userInfo: [
72                 NSLocalizedDescriptionKey: "HomeAppLoaderTask encountered an unexpected error and could not launch an update."
73               ]
74             )
75           )
76         }
77         // swiftlint:enable force_unwrapping
78       }
79     }
80   }
81 
82   private func runReaper(withLauncher launcher: AppLauncher) {
83     if let launchedUpdate = launcher.launchedUpdate {
84       UpdatesReaper.reapUnusedUpdates(
85         withConfig: config,
86         database: database,
87         directory: directory,
88         selectionPolicy: selectionPolicy,
89         launchedUpdate: launchedUpdate
90       )
91     }
92   }
93 
94   private func loadHomeUpdate(withCompletion completion: @escaping (_ remoteError: Error?, _ updateResponse: UpdateResponse?) -> Void) {
95     HomeAppLoader(
96       manifestAndAssetRequestHeaders: self.manifestAndAssetRequestHeaders,
97       config: config,
98       database: database,
99       directory: directory,
100       launchedUpdate: nil,
101       completionQueue: loaderTaskQueue
102     ).loadHome { _ in
103       return true
104     } asset: { _, _, _, _ in
105       // no-op
106     } success: { updateResponse in
107       completion(nil, updateResponse)
108     } error: { error in
109       completion(error, nil)
110     }
111   }
112 
113   private func launchUpdate(_ updateBeingLaunched: Update?, error: Error?) {
114     if let updateBeingLaunched = updateBeingLaunched {
115       let launcher = AppLauncherWithDatabase(
116         config: self.config,
117         database: self.database,
118         directory: self.directory,
119         completionQueue: self.loaderTaskQueue
120       )
121       launcher.launchUpdate(withSelectionPolicy: self.selectionPolicy) { error, success in
122         if success {
123           self.finish(withLauncher: launcher, error: nil)
124         } else {
125           self.finish(withLauncher: nil, error: error)
126           NSLog("Downloaded update but failed to launch: %@", error?.localizedDescription ?? "")
127         }
128         self.runReaper(withLauncher: launcher)
129       }
130     } else {
131       // there's no update, there should be an error
132       self.finish(withLauncher: nil, error: error)
133     }
134   }
135 }
136