diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index b58d21864..e6d91a660 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -583,7 +583,19 @@ - (void)saveFailedUpdate:(NSDictionary *)failedPackage if ([[self class] isFailedHash:[failedPackage objectForKey:PackageHashKey]]) { return; } - + + // The server may return null for optional fields (e.g. assetDownloadUrl, + // bundleDiffBlobUrl), which NSJSONSerialization parses to NSNull. NSNull + // is not a property list type, so passing it to NSUserDefaults raises + // NSInvalidArgumentException ("Attempt to insert non-property list object"). + NSMutableDictionary *sanitizedPackage = [NSMutableDictionary dictionaryWithCapacity:failedPackage.count]; + for (NSString *key in failedPackage) { + id value = failedPackage[key]; + if (value && ![value isKindOfClass:[NSNull class]]) { + sanitizedPackage[key] = value; + } + } + NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey]; if (failedUpdates == nil) { @@ -594,9 +606,18 @@ - (void)saveFailedUpdate:(NSDictionary *)failedPackage failedUpdates = [failedUpdates mutableCopy]; } - [failedUpdates addObject:failedPackage]; - [preferences setObject:failedUpdates forKey:FailedUpdatesKey]; - [preferences synchronize]; + [failedUpdates addObject:sanitizedPackage]; + @try { + [preferences setObject:failedUpdates forKey:FailedUpdatesKey]; + [preferences synchronize]; + } @catch (NSException *exception) { + // Defensive: if a future server response shape slips through the filter, + // clear the corrupt list so the app does not crash on every subsequent + // launch via initializeUpdateAfterRestart -> rollbackPackage. + CPLog(@"Failed to persist failed update list: %@. Clearing key.", exception); + [preferences removeObjectForKey:FailedUpdatesKey]; + [preferences synchronize]; + } } /*