@@ -22,7 +22,7 @@ @implementation CodePush {
22
22
// These keys are already "namespaced" by the PendingUpdateKey, so
23
23
// their values don't need to be obfuscated to prevent collision with app data
24
24
static NSString *const PendingUpdateHashKey = @" hash" ;
25
- static NSString *const PendingUpdateRollbackTimeoutKey = @" rollbackTimeout " ;
25
+ static NSString *const PendingUpdateIsLoadingKey = @" isLoading " ;
26
26
27
27
@synthesize bridge = _bridge;
28
28
@@ -70,20 +70,6 @@ + (NSString *)getApplicationSupportDirectory
70
70
71
71
// Private API methods
72
72
73
- /*
74
- * This method cancels the currently running rollback
75
- * timer, which has the effect of stopping an automatic
76
- * rollback from occurring.
77
- *
78
- * Note: This method is safe to call from any thread.
79
- */
80
- - (void )cancelRollbackTimer
81
- {
82
- dispatch_async (dispatch_get_main_queue (), ^{
83
- [_timer invalidate ];
84
- });
85
- }
86
-
87
73
/*
88
74
* This method is used by the React Native bridge to allow
89
75
* our plugin to expose constants to the JS-side. In our case
@@ -120,26 +106,27 @@ - (instancetype)init
120
106
}
121
107
122
108
/*
123
- * This method starts the rollback protection timer
124
- * and is used when a new update is initialized.
109
+ * This method is used when the app is started to either
110
+ * initialize a pending update or rollback a faulty update
111
+ * to the previous version.
125
112
*/
126
113
- (void )initializeUpdateAfterRestart
127
114
{
128
115
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
129
116
NSDictionary *pendingUpdate = [preferences objectForKey: PendingUpdateKey];
130
-
131
117
if (pendingUpdate) {
132
118
_isFirstRunAfterUpdate = YES ;
133
- int rollbackTimeout = [pendingUpdate[PendingUpdateRollbackTimeoutKey] intValue ];
134
- if (0 != rollbackTimeout) {
135
- dispatch_async (dispatch_get_main_queue (), ^{
136
- [self startRollbackTimer: rollbackTimeout];
137
- });
119
+ BOOL updateIsLoading = [pendingUpdate[PendingUpdateIsLoadingKey] boolValue ];
120
+ if (updateIsLoading) {
121
+ // Pending update was initialized, but notifyApplicationReady was not called.
122
+ // Therefore, deduce that it is a broken update and rollback.
123
+ [self rollbackPackage ];
124
+ } else {
125
+ // Mark that we tried to initialize the new update, so that if it crashes,
126
+ // we will know that we need to rollback when the app next starts.
127
+ [self savePendingUpdate: pendingUpdate[PendingUpdateHashKey]
128
+ isLoading: YES ];
138
129
}
139
-
140
- // Clear the pending update and sync
141
- [preferences removeObjectForKey: PendingUpdateKey];
142
- [preferences synchronize ];
143
130
}
144
131
}
145
132
@@ -161,15 +148,19 @@ - (BOOL)isFailedHash:(NSString*)packageHash
161
148
*/
162
149
- (void )loadBundle
163
150
{
164
- // If the current bundle URL is using http(s), then assume the dev
165
- // is debugging and therefore, shouldn't be redirected to a local
166
- // file (since Chrome wouldn't support it). Otherwise, update
167
- // the current bundle URL to point at the latest update
168
- if (![_bridge.bundleURL.scheme hasPrefix: @" http" ]) {
169
- _bridge.bundleURL = [CodePush bundleURL ];
170
- }
171
-
172
- [_bridge reload ];
151
+ // This needs to be async dispatched because the _bridge is not set on init
152
+ // when the app first starts, therefore rollbacks will not take effect.
153
+ dispatch_async (dispatch_get_main_queue (), ^{
154
+ // If the current bundle URL is using http(s), then assume the dev
155
+ // is debugging and therefore, shouldn't be redirected to a local
156
+ // file (since Chrome wouldn't support it). Otherwise, update
157
+ // the current bundle URL to point at the latest update
158
+ if (![_bridge.bundleURL.scheme hasPrefix: @" http" ]) {
159
+ _bridge.bundleURL = [CodePush bundleURL ];
160
+ }
161
+
162
+ [_bridge reload ];
163
+ });
173
164
}
174
165
175
166
/*
@@ -187,9 +178,9 @@ - (void)rollbackPackage
187
178
// Write the current package's hash to the "failed list"
188
179
[self saveFailedUpdate: packageHash];
189
180
190
- // Do the actual rollback and then
191
- // refresh the app with the previous package
181
+ // Rollback to the previous version and de-register the new update
192
182
[CodePushPackage rollbackPackage ];
183
+ [self removePendingUpdate ];
193
184
[self loadBundle ];
194
185
}
195
186
@@ -215,39 +206,36 @@ - (void)saveFailedUpdate:(NSString *)packageHash
215
206
[preferences synchronize ];
216
207
}
217
208
209
+ /*
210
+ * This method is used to register the fact that a pending
211
+ * update succeeded and therefore can be removed.
212
+ */
213
+ - (void )removePendingUpdate
214
+ {
215
+ NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
216
+ [preferences removeObjectForKey: PendingUpdateKey];
217
+ [preferences synchronize ];
218
+ }
219
+
218
220
/*
219
221
* When an update is installed whose mode isn't IMMEDIATE, this method
220
- * can be called to store the pending update's metadata (e.g. rollbackTimeout )
222
+ * can be called to store the pending update's metadata (e.g. packageHash )
221
223
* so that it can be used when the actual update application occurs at a later point.
222
224
*/
223
225
- (void )savePendingUpdate : (NSString *)packageHash
224
- rollbackTimeout : ( int ) rollbackTimeout
226
+ isLoading : ( BOOL ) isLoading
225
227
{
226
228
// Since we're not restarting, we need to store the fact that the update
227
229
// was installed, but hasn't yet become "active".
228
230
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults ];
229
231
NSDictionary *pendingUpdate = [[NSDictionary alloc ] initWithObjectsAndKeys:
230
232
packageHash,PendingUpdateHashKey,
231
- [NSNumber numberWithInt: rollbackTimeout],PendingUpdateRollbackTimeoutKey , nil ];
233
+ [NSNumber numberWithBool: isLoading],PendingUpdateIsLoadingKey , nil ];
232
234
233
235
[preferences setObject: pendingUpdate forKey: PendingUpdateKey];
234
236
[preferences synchronize ];
235
237
}
236
238
237
- /*
238
- * This method handles starting the actual rollback timer
239
- * after an update has been installed.
240
- */
241
- - (void )startRollbackTimer : (int )rollbackTimeout
242
- {
243
- double timeoutInSeconds = rollbackTimeout / 1000 ;
244
- _timer = [NSTimer scheduledTimerWithTimeInterval: timeoutInSeconds
245
- target: self
246
- selector: @selector (rollbackPackage )
247
- userInfo: nil
248
- repeats: NO ];
249
- }
250
-
251
239
// JavaScript-exported module methods
252
240
253
241
/*
@@ -318,7 +306,6 @@ - (void)startRollbackTimer:(int)rollbackTimeout
318
306
* This method is the native side of the LocalPackage.install method.
319
307
*/
320
308
RCT_EXPORT_METHOD (installUpdate:(NSDictionary *)updatePackage
321
- rollbackTimeout:(int )rollbackTimeout
322
309
installMode:(CodePushInstallMode)installMode
323
310
resolver:(RCTPromiseResolveBlock)resolve
324
311
rejecter:(RCTPromiseRejectBlock)reject)
@@ -332,7 +319,7 @@ - (void)startRollbackTimer:(int)rollbackTimeout
332
319
reject (error);
333
320
} else {
334
321
[self savePendingUpdate: updatePackage[@" packageHash" ]
335
- rollbackTimeout: rollbackTimeout ];
322
+ isLoading: NO ];
336
323
337
324
if (installMode == CodePushInstallModeImmediate) {
338
325
[self loadBundle ];
@@ -389,7 +376,7 @@ - (void)startRollbackTimer:(int)rollbackTimeout
389
376
RCT_EXPORT_METHOD (notifyApplicationReady:(RCTPromiseResolveBlock)resolve
390
377
rejecter:(RCTPromiseRejectBlock)reject)
391
378
{
392
- [self cancelRollbackTimer ];
379
+ [self removePendingUpdate ];
393
380
resolve ([NSNull null ]);
394
381
}
395
382
0 commit comments