1 // Copyright © 2019 650 Industries. All rights reserved.
2
3 import Foundation
4 import ABI49_0_0EXManifests
5
assertType<T>null6 func assertType<T>(value: Any, description: String) -> T {
7 if !(value is T) {
8 let exception = NSException(
9 name: NSExceptionName.internalInconsistencyException,
10 reason: description,
11 userInfo: [:]
12 )
13 exception.raise()
14 }
15
16 // exception above will preempt force_cast
17 // swiftlint:disable:next force_cast
18 return value as! T
19 }
20
21 public extension Optional {
requirenull22 func require(_ desc: String) -> Wrapped {
23 if self == nil {
24 let exception = NSException(
25 name: NSExceptionName.internalInconsistencyException,
26 reason: desc,
27 userInfo: [:]
28 )
29 exception.raise()
30 }
31
32 // exception above will preempt force_unwrapping
33 // swiftlint:disable:next force_unwrapping
34 return self!
35 }
36 }
37
38 /**
39 * Download status that indicates whether or under what conditions an
40 * update is able to be launched.
41 *
42 * It's important that the integer value of each status stays constant across
43 * all versions of this library since they are stored in SQLite on user devices.
44 */
45 @objc(ABI49_0_0EXUpdatesUpdateStatus)
46 public enum UpdateStatus: Int {
47 case Status0_Unused = 0
48 /**
49 * The update has been fully downloaded and is ready to launch.
50 */
51 case StatusReady = 1
52 case Status2_Unused = 2
53 /**
54 * The update manifest has been download from the server but not all
55 * assets have finished downloading successfully.
56 */
57 case StatusPending = 3
58 case Status4_Unused = 4
59 /**
60 * The update has been partially loaded (copied) from its location
61 * embedded in the app bundle, but not all assets have been copied
62 * successfully. The update may be able to be launched directly from
63 * its embedded location unless a new binary version with a new
64 * embedded update has been installed.
65 */
66 case StatusEmbedded = 5
67 /**
68 * The update manifest has been downloaded and indicates that the
69 * update is being served from a developer tool. It can be launched by a
70 * host application that can run a development bundle.
71 */
72 case StatusDevelopment = 6
73 }
74
75 @objc(ABI49_0_0EXUpdatesUpdateError)
76 public enum UpdateError: Int, Error {
77 case invalidExpoProtocolVersion
78 }
79
80 @objc(ABI49_0_0EXUpdatesUpdate)
81 @objcMembers
82 public class Update: NSObject {
83 public let updateId: UUID
84 public let scopeKey: String?
85 public var commitTime: Date
86 public let runtimeVersion: String
87 public let keep: Bool
88 public let isDevelopmentMode: Bool
89 private let assetsFromManifest: [UpdateAsset]?
90
91 public let manifest: Manifest
92
93 public var status: UpdateStatus
94 public var lastAccessed: Date
95 public var successfulLaunchCount: Int
96 public var failedLaunchCount: Int
97
98 private let config: UpdatesConfig
99 private let database: UpdatesDatabase?
100
101 public init(
102 manifest: Manifest,
103 config: UpdatesConfig,
104 database: UpdatesDatabase?,
105 updateId: UUID,
106 scopeKey: String?,
107 commitTime: Date,
108 runtimeVersion: String,
109 keep: Bool,
110 status: UpdateStatus,
111 isDevelopmentMode: Bool,
112 assetsFromManifest: [UpdateAsset]?
113 ) {
114 self.updateId = updateId
115 self.commitTime = commitTime
116 self.runtimeVersion = runtimeVersion
117 self.keep = keep
118 self.manifest = manifest
119 self.config = config
120 self.database = database
121 self.scopeKey = scopeKey
122 self.status = status
123 self.assetsFromManifest = assetsFromManifest
124
125 self.lastAccessed = Date()
126 self.successfulLaunchCount = 0
127 self.failedLaunchCount = 0
128 self.isDevelopmentMode = isDevelopmentMode
129 }
130
131 internal static func update(
132 withManifest: [String: Any],
133 responseHeaderData: ResponseHeaderData,
134 extensions: [String: Any],
135 config: UpdatesConfig,
136 database: UpdatesDatabase
137 ) throws -> Update {
138 let protocolVersion = responseHeaderData.protocolVersion
139 switch protocolVersion {
140 case nil:
141 return LegacyUpdate.update(
142 withLegacyManifest: LegacyManifest(rawManifestJSON: withManifest),
143 config: config,
144 database: database
145 )
146 case 0, 1:
147 return NewUpdate.update(
148 withNewManifest: NewManifest(rawManifestJSON: withManifest),
149 extensions: extensions,
150 config: config,
151 database: database
152 )
153 default:
154 throw UpdateError.invalidExpoProtocolVersion
155 }
156 }
157
158 public static func update(
159 withEmbeddedManifest: [String: Any],
160 config: UpdatesConfig,
161 database: UpdatesDatabase?
162 ) -> Update {
163 if withEmbeddedManifest["releaseId"] != nil {
164 return LegacyUpdate.update(
165 withLegacyManifest: LegacyManifest(rawManifestJSON: withEmbeddedManifest),
166 config: config,
167 database: database
168 )
169 } else {
170 return BareUpdate.update(
171 withBareManifest: BareManifest(rawManifestJSON: withEmbeddedManifest),
172 config: config,
173 database: database
174 )
175 }
176 }
177
178 /**
179 * Accessing this property may lazily load the assets from the database, if this update object
180 * originated from the database.
181 */
assetsnull182 public func assets() -> [UpdateAsset]? {
183 guard let assetsFromManifest = self.assetsFromManifest else {
184 return self.assetsFromDatabase()
185 }
186 return assetsFromManifest
187 }
188
assetsFromDatabasenull189 private func assetsFromDatabase() -> [UpdateAsset]? {
190 guard let database = self.database else {
191 return nil
192 }
193
194 var assetsLocal: [UpdateAsset] = []
195 database.databaseQueue.sync {
196 // The pattern is valid, so it'll never throw
197 // swiftlint:disable:next force_try
198 assetsLocal = try! database.assets(withUpdateId: self.updateId)
199 }
200 return assetsLocal
201 }
202
loggingIdnull203 public func loggingId() -> String {
204 self.updateId.uuidString.lowercased()
205 }
206 }
207