Skip to content

Commit be5de49

Browse files
authored
Bug fix: Correctly save objectId of Installation (#116)
* Bug fix: Save objectId of Installation to Keychain * Changelog entry * nit * Disable test that doesn't work on Linux * Update .codecov.yml * Update .codecov.yml * Update changelog * Improve codecov * Update .codecov.yml * Remove requirement of being logged in to save current installation and config to keychain * regression in playgrounds. Add directions for addressing update in changelog * Migrate old installations automatically * Testing Linux * Don't run migration test on Linux * nits
1 parent f4ee1b9 commit be5de49

File tree

17 files changed

+518
-149
lines changed

17 files changed

+518
-149
lines changed

.codecov.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ coverage:
55
status:
66
patch:
77
default:
8-
target: auto
8+
target: 58
99
changes: false
1010
project:
1111
default:

CHANGELOG.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
__Improvements__
1111
- (Breaking Change) No longer require dispatch to main queue when using ParseInstallation. The side effect of this is bade is no longer retrieved by the SDK. The developer should retrieve the badge count on their own and save it to `ParseInstallation` if they require badge ([#114](https://github.com/parse-community/Parse-Swift/pull/114)), thanks to [Corey Baker](https://github.com/cbaker6).
1212

13+
__Fixes__
14+
- (Breaking Change) Correctly saves objectId of ParseInstallation to Keychain when saving to server. Also fixes issue when using deleteAll with current ParseUser and ParseInstallation. Old installations will automatically be migrated to the new one. If you end up having issues you can delete all of the installations in your ParseDashboard that were created with Parse-Swift < 1.30. If you are not able to do this, you can all log out of devices using Parse-Swift < 1.30 and then log back in ([#116](https://github.com/parse-community/Parse-Swift/pull/116)), thanks to [Corey Baker](https://github.com/cbaker6).
15+
1316
### 1.2.6
1417
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.2.5...1.2.6)
1518

@@ -58,14 +61,12 @@ __Fixes__
5861
### 1.2.0
5962
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.1.6...1.2.0)
6063

61-
__Breaking changes__
62-
- Allows return types to be specified for `ParseCloud`, query `hint`, and `explain` (see playgrounds for examples). Changed functionality of synchronous `query.first()`. It use to return nil if no values are found. Now it will throw an error if none are found. ([#92](https://github.com/parse-community/Parse-Swift/pull/92)), thanks to [Corey Baker](https://github.com/cbaker6).
63-
6464
__New features__
6565
- Add transaction support to batch saveAll and deleteAll ([#89](https://github.com/parse-community/Parse-Swift/pull/89)), thanks to [Corey Baker](https://github.com/cbaker6).
6666
- Add modifiers to containsString, hasPrefix, hasSuffix ([#85](https://github.com/parse-community/Parse-Swift/pull/85)), thanks to [Corey Baker](https://github.com/cbaker6).
6767

6868
__Improvements__
69+
- (Breaking Change) Allows return types to be specified for `ParseCloud`, query `hint`, and `explain` (see playgrounds for examples). Changed functionality of synchronous `query.first()`. It use to return nil if no values are found. Now it will throw an error if none are found. ([#92](https://github.com/parse-community/Parse-Swift/pull/92)), thanks to [Corey Baker](https://github.com/cbaker6).
6970
- Better error reporting when decode errors occur ([#92](https://github.com/parse-community/Parse-Swift/pull/92)), thanks to [Corey Baker](https://github.com/cbaker6).
7071
- Can use a variadic version of exclude. Added examples of select and exclude query in playgrounds ([#88](https://github.com/parse-community/Parse-Swift/pull/88)), thanks to [Corey Baker](https://github.com/cbaker6).
7172

ParseSwift.playground/Pages/2 - Finding Objects.xcplaygroundpage/Contents.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ struct GameScore: ParseObject {
2121

2222
//: Your own properties.
2323
var score: Int?
24-
var timeStamp = Date()
24+
var timeStamp: Date? = Date()
2525
var oldScore: Int?
2626
}
2727

ParseSwift.playground/Pages/4 - User - Continued.xcplaygroundpage/Contents.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ do {
131131
print("Error logging out: \(error)")
132132
}
133133

134-
//: Password Reset Request - synchronously.
134+
//: Verification Email - synchronously.
135135
do {
136136
try User.verificationEmail(email: "[email protected]")
137137
print("Successfully requested verification email be sent")

ParseSwift.playground/Pages/6 - Installation.xcplaygroundpage/Contents.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,21 @@ Installation.current?.save { results in
5353
}
5454
}
5555

56+
/*: Update your `ParseInstallation` `customKey` value.
57+
Performs work on background queue and returns to designated on
58+
designated callbackQueue. If no callbackQueue is specified it
59+
returns to main queue.
60+
*/
61+
Installation.current?.customKey = "updatedValue"
62+
Installation.current?.save { results in
63+
64+
switch results {
65+
case .success(let updatedInstallation):
66+
print("Successfully save myCustomInstallationKey to ParseServer: \(updatedInstallation)")
67+
case .failure(let error):
68+
print("Failed to update installation: \(error)")
69+
}
70+
}
71+
5672
PlaygroundPage.current.finishExecution()
5773
//: [Next](@next)

ParseSwift.playground/Pages/7 - GeoPoint.xcplaygroundpage/Contents.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ score.save { result in
5858
case .failure(let error):
5959
assertionFailure("Error saving: \(error)")
6060
}
61-
6261
}
6362

6463
//: Now we will show how to query based on the `ParseGeoPoint`.

ParseSwift.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,9 @@
295295
70D1BE7425BB43EB00A42E7C /* BaseConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D1BE7225BB43EB00A42E7C /* BaseConfig.swift */; };
296296
70D1BE7525BB43EB00A42E7C /* BaseConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D1BE7225BB43EB00A42E7C /* BaseConfig.swift */; };
297297
70D1BE7625BB43EB00A42E7C /* BaseConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70D1BE7225BB43EB00A42E7C /* BaseConfig.swift */; };
298+
70DFEA8A2618E77800F8EB4B /* MigrateOldParseInstallationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70DFEA892618E77800F8EB4B /* MigrateOldParseInstallationTests.swift */; };
299+
70DFEA8B2618E77800F8EB4B /* MigrateOldParseInstallationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70DFEA892618E77800F8EB4B /* MigrateOldParseInstallationTests.swift */; };
300+
70DFEA8C2618E77800F8EB4B /* MigrateOldParseInstallationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70DFEA892618E77800F8EB4B /* MigrateOldParseInstallationTests.swift */; };
298301
70F2E255254F247000B2EA5C /* ParseSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AFDA7121F26D9A5002AE4FC /* ParseSwift.framework */; };
299302
70F2E2B3254F283000B2EA5C /* ParseUserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70C7DC1D24D20E530050419B /* ParseUserTests.swift */; };
300303
70F2E2B4254F283000B2EA5C /* ParseQueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70C7DC1F24D20F180050419B /* ParseQueryTests.swift */; };
@@ -632,6 +635,7 @@
632635
70D1BDB925BB17A600A42E7C /* ParseConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfig.swift; sourceTree = "<group>"; };
633636
70D1BE0625BB2BF400A42E7C /* ParseConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfigTests.swift; sourceTree = "<group>"; };
634637
70D1BE7225BB43EB00A42E7C /* BaseConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseConfig.swift; sourceTree = "<group>"; };
638+
70DFEA892618E77800F8EB4B /* MigrateOldParseInstallationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateOldParseInstallationTests.swift; sourceTree = "<group>"; };
635639
70F2E23E254F246000B2EA5C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
636640
70F2E250254F247000B2EA5C /* ParseSwiftTestsmacOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ParseSwiftTestsmacOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
637641
70F2E254254F247000B2EA5C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -810,6 +814,7 @@
810814
7003957525A0EE770052CB31 /* BatchUtilsTests.swift */,
811815
705726ED2592C91C00F0ADD5 /* HashTests.swift */,
812816
4AA8076E1F794C1C008CD551 /* KeychainStoreTests.swift */,
817+
70DFEA892618E77800F8EB4B /* MigrateOldParseInstallationTests.swift */,
813818
9194657724F16E330070296B /* ParseACLTests.swift */,
814819
7044C22C25C5E4E90011F6E7 /* ParseAnonymousCombineTests.swift */,
815820
70A2D86A25B3ADB6001BEB7D /* ParseAnonymousTests.swift */,
@@ -1668,6 +1673,7 @@
16681673
70732C5A2606CCAD000CAB81 /* ParseObjectCustomObjectIdTests.swift in Sources */,
16691674
911DB12C24C3F7720027F3C7 /* MockURLResponse.swift in Sources */,
16701675
7044C24325C5EA360011F6E7 /* ParseAppleCombineTests.swift in Sources */,
1676+
70DFEA8A2618E77800F8EB4B /* MigrateOldParseInstallationTests.swift in Sources */,
16711677
7044C1DF25C5C70D0011F6E7 /* ParseObjectCombine.swift in Sources */,
16721678
89899D9F26045998002E2043 /* ParseTwitterCombineTests.swift in Sources */,
16731679
70C5504625B40D5200B5DBC2 /* ParseSessionTests.swift in Sources */,
@@ -1819,6 +1825,7 @@
18191825
70732C5C2606CCAD000CAB81 /* ParseObjectCustomObjectIdTests.swift in Sources */,
18201826
709B984D2556ECAA00507778 /* AnyDecodableTests.swift in Sources */,
18211827
7044C24525C5EA360011F6E7 /* ParseAppleCombineTests.swift in Sources */,
1828+
70DFEA8C2618E77800F8EB4B /* MigrateOldParseInstallationTests.swift in Sources */,
18221829
7044C1E125C5C70D0011F6E7 /* ParseObjectCombine.swift in Sources */,
18231830
89899DA126045998002E2043 /* ParseTwitterCombineTests.swift in Sources */,
18241831
70C5504825B40D5200B5DBC2 /* ParseSessionTests.swift in Sources */,
@@ -1873,6 +1880,7 @@
18731880
70732C5B2606CCAD000CAB81 /* ParseObjectCustomObjectIdTests.swift in Sources */,
18741881
70F2E2C2254F283000B2EA5C /* APICommandTests.swift in Sources */,
18751882
7044C24425C5EA360011F6E7 /* ParseAppleCombineTests.swift in Sources */,
1883+
70DFEA8B2618E77800F8EB4B /* MigrateOldParseInstallationTests.swift in Sources */,
18761884
7044C1E025C5C70D0011F6E7 /* ParseObjectCombine.swift in Sources */,
18771885
89899DA026045998002E2043 /* ParseTwitterCombineTests.swift in Sources */,
18781886
70C5504725B40D5200B5DBC2 /* ParseSessionTests.swift in Sources */,

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import PackageDescription
4444
let package = Package(
4545
name: "YOUR_PROJECT_NAME",
4646
dependencies: [
47-
.package(url: "https://github.com/parse-community/Parse-Swift", from: "1.1.2"),
47+
.package(url: "https://github.com/parse-community/Parse-Swift", from: "1.3.0"),
4848
]
4949
)
5050
```

Sources/ParseSwift/Objects/ParseInstallation.swift

Lines changed: 67 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ extension ParseInstallation {
100100
}
101101
}
102102

103+
func hasSameInstallationId<T: ParseInstallation>(as other: T) -> Bool {
104+
return other.className == className && other.installationId == installationId && installationId != nil
105+
}
106+
103107
/**
104108
Sets the device token string property from an `Data`-encoded token.
105109
- parameter data: A token that identifies the device.
@@ -303,23 +307,22 @@ extension ParseInstallation {
303307

304308
// MARK: Fetchable
305309
extension ParseInstallation {
306-
internal static func updateKeychainIfNeeded(_ results: [Self], deleting: Bool = false) {
307-
guard BaseParseUser.current != nil,
308-
let currentInstallation = BaseParseInstallation.current else {
310+
internal static func updateKeychainIfNeeded(_ results: [Self], deleting: Bool = false) throws {
311+
guard let currentInstallation = BaseParseInstallation.current else {
309312
return
310313
}
311314

312-
var saveInstallation: Self?
313-
let foundCurrentInstallationObjects = results.filter { $0.hasSameObjectId(as: currentInstallation) }
315+
var foundCurrentInstallationObjects = results.filter { $0.hasSameInstallationId(as: currentInstallation) }
316+
foundCurrentInstallationObjects = try foundCurrentInstallationObjects.sorted(by: {
317+
if $0.updatedAt == nil || $1.updatedAt == nil {
318+
throw ParseError(code: .unknownError,
319+
message: "Objects from the server should always have an 'updatedAt'")
320+
}
321+
return $0.updatedAt!.compare($1.updatedAt!) == .orderedDescending
322+
})
314323
if let foundCurrentInstallation = foundCurrentInstallationObjects.first {
315-
saveInstallation = foundCurrentInstallation
316-
} else {
317-
saveInstallation = results.first
318-
}
319-
320-
if saveInstallation != nil {
321324
if !deleting {
322-
Self.current = saveInstallation
325+
Self.current = foundCurrentInstallation
323326
Self.saveCurrentContainerToKeychain()
324327
} else {
325328
Self.deleteCurrentContainerFromKeychain()
@@ -341,7 +344,7 @@ extension ParseInstallation {
341344
options: API.Options = []) throws -> Self {
342345
let result: Self = try fetchCommand(include: includeKeys)
343346
.execute(options: options, callbackQueue: .main)
344-
Self.updateKeychainIfNeeded([result])
347+
try Self.updateKeychainIfNeeded([result])
345348
return result
346349
}
347350

@@ -369,9 +372,21 @@ extension ParseInstallation {
369372
callbackQueue: callbackQueue) { result in
370373
callbackQueue.async {
371374
if case .success(let foundResult) = result {
372-
Self.updateKeychainIfNeeded([foundResult])
375+
do {
376+
try Self.updateKeychainIfNeeded([foundResult])
377+
completion(.success(foundResult))
378+
} catch {
379+
let returnError: ParseError!
380+
if let parseError = error as? ParseError {
381+
returnError = parseError
382+
} else {
383+
returnError = ParseError(code: .unknownError, message: error.localizedDescription)
384+
}
385+
completion(.failure(returnError))
386+
}
387+
} else {
388+
completion(result)
373389
}
374-
completion(result)
375390
}
376391
}
377392
} catch {
@@ -438,7 +453,7 @@ extension ParseInstallation {
438453
callbackQueue: .main,
439454
childObjects: childObjects,
440455
childFiles: childFiles)
441-
Self.updateKeychainIfNeeded([result])
456+
try Self.updateKeychainIfNeeded([result])
442457
return result
443458
}
444459

@@ -466,10 +481,22 @@ extension ParseInstallation {
466481
childFiles: savedChildFiles) { result in
467482
callbackQueue.async {
468483
if case .success(let foundResults) = result {
469-
Self.updateKeychainIfNeeded([foundResults])
484+
do {
485+
try Self.updateKeychainIfNeeded([foundResults])
486+
completion(.success(foundResults))
487+
} catch {
488+
let returnError: ParseError!
489+
if let parseError = error as? ParseError {
490+
returnError = parseError
491+
} else {
492+
returnError = ParseError(code: .unknownError,
493+
message: error.localizedDescription)
494+
}
495+
completion(.failure(returnError))
496+
}
497+
} else {
498+
completion(result)
470499
}
471-
472-
completion(result)
473500
}
474501
}
475502
} catch {
@@ -533,7 +560,7 @@ extension ParseInstallation {
533560
*/
534561
public func delete(options: API.Options = []) throws {
535562
_ = try deleteCommand().execute(options: options)
536-
Self.updateKeychainIfNeeded([self], deleting: true)
563+
try Self.updateKeychainIfNeeded([self], deleting: true)
537564
}
538565

539566
/**
@@ -558,8 +585,18 @@ extension ParseInstallation {
558585
switch result {
559586

560587
case .success:
561-
Self.updateKeychainIfNeeded([self], deleting: true)
562-
completion(.success(()))
588+
do {
589+
try Self.updateKeychainIfNeeded([self], deleting: true)
590+
completion(.success(()))
591+
} catch {
592+
let returnError: ParseError!
593+
if let parseError = error as? ParseError {
594+
returnError = parseError
595+
} else {
596+
returnError = ParseError(code: .unknownError, message: error.localizedDescription)
597+
}
598+
completion(.failure(returnError))
599+
}
563600
case .failure(let error):
564601
completion(.failure(error))
565602
}
@@ -678,7 +715,7 @@ public extension Sequence where Element: ParseInstallation {
678715
childFiles: childFiles)
679716
returnBatch.append(contentsOf: currentBatch)
680717
}
681-
Self.Element.updateKeychainIfNeeded(returnBatch.compactMap {try? $0.get()})
718+
try Self.Element.updateKeychainIfNeeded(returnBatch.compactMap {try? $0.get()})
682719
return returnBatch
683720
}
684721

@@ -779,7 +816,7 @@ public extension Sequence where Element: ParseInstallation {
779816
returnBatch.append(contentsOf: saved)
780817
if completed == (batches.count - 1) {
781818
callbackQueue.async {
782-
Self.Element.updateKeychainIfNeeded(returnBatch.compactMap {try? $0.get()})
819+
try? Self.Element.updateKeychainIfNeeded(returnBatch.compactMap {try? $0.get()})
783820
completion(.success(returnBatch))
784821
}
785822
}
@@ -842,7 +879,7 @@ public extension Sequence where Element: ParseInstallation {
842879
message: "objectId \"\(uniqueObjectId)\" was not found in className \"\(Self.Element.className)\"")))
843880
}
844881
}
845-
Self.Element.updateKeychainIfNeeded(fetchedObjects)
882+
try Self.Element.updateKeychainIfNeeded(fetchedObjects)
846883
return fetchedObjectsToReturn
847884
} else {
848885
throw ParseError(code: .unknownError, message: "all items to fetch must be of the same class")
@@ -891,7 +928,7 @@ public extension Sequence where Element: ParseInstallation {
891928
}
892929
}
893930
callbackQueue.async {
894-
Self.Element.updateKeychainIfNeeded(fetchedObjects)
931+
try? Self.Element.updateKeychainIfNeeded(fetchedObjects)
895932
completion(.success(fetchedObjectsToReturn))
896933
}
897934
case .failure(let error):
@@ -950,7 +987,8 @@ public extension Sequence where Element: ParseInstallation {
950987
returnBatch.append(contentsOf: currentBatch)
951988
}
952989

953-
Self.Element.updateKeychainIfNeeded(compactMap {$0})
990+
try Self.Element.updateKeychainIfNeeded(compactMap {$0},
991+
deleting: true)
954992
return returnBatch
955993
}
956994

@@ -1006,7 +1044,8 @@ public extension Sequence where Element: ParseInstallation {
10061044
returnBatch.append(contentsOf: saved)
10071045
if completed == (batches.count - 1) {
10081046
callbackQueue.async {
1009-
Self.Element.updateKeychainIfNeeded(self.compactMap {$0})
1047+
try? Self.Element.updateKeychainIfNeeded(self.compactMap {$0},
1048+
deleting: true)
10101049
completion(.success(returnBatch))
10111050
}
10121051
}

0 commit comments

Comments
 (0)