CloudKit Error: Change Token Expired, Reset Needed - cloudkit

Swift 3.1, Xcode 8.3.3
I keep getting an error from CloudKit and I don't know what to do about it.
I'm tracking notifications from CloudKit like this:
let operation = CKFetchNotificationChangesOperation(previousServerChangeToken: previousChangeToken)
//Hold the notification IDs we processed so we can tell CloudKit to never send them to us again
var notificationIDs = [CKNotificationID]()
operation.notificationChangedBlock = { [weak self] notification in
guard let notification = notification as? CKQueryNotification else { return }
if let id = notification.notificationID {
notificationIDs.append(id)
}
}
operation.fetchNotificationChangesCompletionBlock = { [weak self] newToken, error in
if let error = error{
print(error) //<-- <!> This is the error <!>
}else{
self?.previousChangeToken = newToken
//All records are in, now save the data locally
let fetchOperation = CKFetchRecordsOperation(recordIDs: recordIDs)
fetchOperation.fetchRecordsCompletionBlock = { [weak self] records, error in
if let e = error {
print("fetchRecordsCompletionBlock Error fetching: \(e)")
}
//Save records to local persistence...
}
self?.privateDB.add(fetchOperation)
//Tell CloudKit we've read the notifications
let operationRead = CKMarkNotificationsReadOperation(notificationIDsToMarkRead: notificationIDs)
self?.container.add(operationRead)
}
}
container.add(operation)
And the error says:
<CKError 0x174241e90: "Change Token Expired" (21/1016); server message
= "Error code: RESET_NEEDED"; uuid = ...; container ID = "...">
The CKServerChangeToken documentation don't mention anything about resetting the token, and the CloudKit dashboard doesn't offer any such option.
Any idea what I'm supposed to do?

This error code is CKErrorCodeChangeTokenExpired, and it's an indication that you need to re-sync your changes.
https://developer.apple.com/documentation/cloudkit/ckerror/2325216-changetokenexpired
This error code gets returned when the change token is too old, or the container has been reset (resetting the container invalidates old change tokens).
The comments related to this error code include:
(Describing the code itself):
The previousServerChangeToken value is too old and the client must re-sync from scratch
(On various fetch operation completion/updated blocks):
If the server returns a CKErrorChangeTokenExpired error, the serverChangeToken used for this record zone when initting this operation was too old and the client should toss its local cache and re-fetch the changes in this record zone starting with a nil serverChangeToken.

Related

Delete document from cloud firestore

I am trying to implement a function that allows the user to delete chats that they have with other users.
At the moment the code works and the firestore document is deleted successfully however as soon as the delete happens the code crashes and i get this error "Fatal error: Unexpectedly found nil while unwrapping an Optional value" next to the id = data!["id"] in the code. I guess that after the delete happens firestore keeps listening for documents and finds an empty collection following the delete. Does anyone know how to stop this from happening?
public func deleteConversation(conversationId: String, completion: #escaping (Bool) -> Void) {
// Get all conversations for current user
let CurrentUser = Auth.auth().currentUser?.uid
let db = Firestore.firestore()
let ConversationRef = db.collection("users").document(CurrentUser!).collection("conversations").document(test!)
print("deleting conversation with \(test!)")
ConversationRef.addSnapshotListener { snapshot, error in
guard let document = snapshot else {
print("Error getting documents: \(String(describing: error))")
return
}
let data = document.data()
if let id = data!["id"] as? String, id == conversationId {
print("conversation found")
ConversationRef.delete()
}
completion(true)
print("deleted conversation")
}
}
The problem comes from:
ConversationRef.addSnapshotListener { snapshot, error in
By calling addSnapshotListener you're adding a listener that:
Gets the document snapshot straight away,
Continues listening for changes and calls your code again then there are any.
The problem is in #2, as it means your code executes again when the document is deleted, and at that point document.data() will be nil.
The simplest fix is to only read the document once:
ConversationRef.getDocument { (document, error) in

Getting an error when trying to upload image to firebase (Swift)

I have the following code:
func upload() {
let localFile = usersImg!
// Create the file metadata
let metadata = StorageMetadata()
metadata.contentType = "image/jpeg"
let storageRef = Storage.storage().reference()
// Upload file and metadata to the object 'images/mountains.jpg'
let uploadTask = storageRef.putFile(from: localFile, metadata: metadata)
// Listen for state changes, errors, and completion of the upload.
uploadTask.observe(.resume) { snapshot in
// Upload resumed, also fires when the upload starts
}
uploadTask.observe(.pause) { snapshot in
// Upload paused
}
uploadTask.observe(.progress) { snapshot in
// Upload reported progress
let percentComplete = 100.0 * Double(snapshot.progress!.completedUnitCount)
/ Double(snapshot.progress!.totalUnitCount)
}
uploadTask.observe(.success) { snapshot in
// Upload completed successfully
}
uploadTask.observe(.failure) { snapshot in
if let error = snapshot.error as NSError? {
switch (StorageErrorCode(rawValue: error.code)!) {
case .objectNotFound:
// File doesn't exist
break
case .unauthorized:
// User doesn't have permission to access file
break
case .cancelled:
// User canceled the upload
break
/* ... */
case .unknown:
// Unknown error occurred, inspect the server response
break
default:
// A separate error occurred. This is a good place to retry the upload.
break
}
}
}
}
The userImg variable is just the image the user selected, if I were to print it out it would look something like this:
Optional(file:///var/mobile/Media/DCIM/100APPLE/IMG_0030.PNG)
However when I run my code and call the method I get the following error:
Thread 12: "*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[1]"
What does that mean and what am I doing wrong. (By the way I have tried a lot of different methods to upload urls to firebase but with everyone I am getting weird errors so this code is straight from firebase website.
I cannot guarantee anything because I didn’t use this same code although I had to change the rules for my firebase storage and that helped!

CloudKit was Working but Getting a Permission Error All of a Sudden

I had CloudKit working for the last couple weeks and this morning when I was trying to save a record, something I had done a number of times before, I started getting a Permission Error
Error saving to CloudKit: <CKError 0x600001f48ed0: "Permission Failure"
(10/2007); server message = "Operation not permitted"; uuid = 78FA3DD1-
EA44-4701-9A7E-8291F076DD8F; container ID = "[CloudKit Container Name]"> - Error fetching auth tokens from server:
Operation not permitted'
So I have triple checked that it's requesting to the right container, which it is (I only have two containers setup on my CloudKit so it's an easy check.
I have turned off and turned CloudKit on again in XCode as an attempt to force reset it (not sure if that did anything helpful but figured I would give it a shot)
In case this is needed, this is how I am saving to CloudKit (had no problem with this bit of code in the past.
func createRecord(title: String, type: String, comment: String) {
let audioRecord = CKRecord(recordType: "Audio")
audioRecord["title"] = title as CKRecordValue
audioRecord["type"] = type as CKRecordValue
audioRecord["comment"] = comment as CKRecordValue
let audioURL = audioRecorder.getAudioURL()
let audioAsset = CKAsset(fileURL: audioURL)
audioRecord["audio"] = audioAsset
DispatchQueue.main.async {
CKContainer.default().publicCloudDatabase.save(audioRecord) { [self] record, error in
//print(CKContainer.default())
if let error = error {
print("Error saving to CloudKit: \(error.self) - \(error.localizedDescription)")
} else {
print("Record has been successfully saved to CloudKit")
}
}
}
}

Receipt validation iOS - The file could not be opened because there is no such file

I'm working on a subscription IAP. I set all purchases up, their details come back well, I can do the purchases in sandbox and get all the messages alright. The problem I have now is checking the receipt. I always get a URL returned alright, but when I try to read it I keep getting error that the file does not exist. So I try and refresh with SKReceiptRefreshRequest. Try again, still same.
I have uninstalled app on simulator and two real devices, try again from new install and same problem. One thing I realised, one of the real devices displays the password prompt request with [Sandbox] mention. However after two prompts (including accepting password), instead of purchase completed I get a "user/password don't match" message. On simulator when prompted for itunes account and password it all goes through but the actual purchase confirmation never comes (I waited 4 minutes, stable internet connection).
This is the validation process (I have changed it quite a few times, from different tutorials and other people's problems)
let receiptURL = Bundle.main.appStoreReceiptURL
func receiptValidation() {
print("1")
print("2", receiptURL)
do {
print("3")
let receiptData = try Data(contentsOf: receiptURL!, options: .alwaysMapped)
print(receiptData)
let receiptString = receiptData.base64EncodedString(options: [])
let dict = ["receipt-data" : receiptString, "password" : "\(password)"] as [String : Any]
do {
print("4")
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: .prettyPrinted)
if let sandboxURL = Foundation.URL(string:"https://sandbox.itunes.apple.com/verifyReceipt") {
print("5")
var request = URLRequest(url: sandboxURL)
request.httpMethod = "POST"
request.httpBody = jsonData
let session = URLSession(configuration: URLSessionConfiguration.default)
let task = session.dataTask(with: request) { data, response, error in
print("6")
if let receivedData = data,
let httpResponse = response as? HTTPURLResponse,
error == nil,
httpResponse.statusCode == 200 {
print("7")
do {
print("8")
if let jsonResponse = try JSONSerialization.jsonObject(with: receivedData, options: JSONSerialization.ReadingOptions.mutableContainers) as? Dictionary<String, AnyObject> {
print(jsonResponse, jsonResponse.count)
// parse and verify the required informatin in the jsonResponse
} else { print("Failed to cast serialized JSON to Dictionary<String, AnyObject>") }
}
catch { print("Couldn't serialize JSON with error: " + error.localizedDescription) }
}
}
print("51")
task.resume()
} else { print("Couldn't convert string into URL. Check for special characters.") }
}
catch { print("Couldn't create JSON with error: " + error.localizedDescription) }
}
catch {
let appReceiptRefreshRequest = SKReceiptRefreshRequest(receiptProperties: nil)
appReceiptRefreshRequest.delegate = self
appReceiptRefreshRequest.start()
print("Couldn't read receipt data with error: " + error.localizedDescription) }
}
func requestDidFinish(_ request: SKRequest) {
print("???")
do {
let receipt = try Data(contentsOf: receiptURL!) //force unwrap is safe here, control can't land here if receiptURL is nil
print(receipt)
} catch {
print("WTF NO RECEIPT")
// still no receipt, possible but unlikely to occur since this is the "success" delegate method
}
}
And this is the Debugging output from running the app. receiptURL varies between simulator/real device, but other then that everything remains the same.
1
2 Optional(file:///Users/apple/Library/Developer/CoreSimulator/Devices/47EA3293-9B13-4808-BD0B-13D884D14BFE/data/Containers/Data/Application/2F1B7E4E-C523-4270-BF46-6D77F7A2220C/StoreKit/receipt)
3
Couldn't read receipt data with error: The file “receipt” couldn’t be opened because there is no such file.
???
WTF NO RECEIPT
???
WTF NO RECEIPT
Why can't I get the receipt created, or found? Is it a device problem, a bug or am I oblivious to something?
The whole IAP process works asynchronously due to which you will not receive the receipt data unless the whole process has been completed successfully. I can't see the whole code based on what you have pasted in your question above but if you are trying to access the receipt data immediately on the action of a button or something similar, you will not get it.
The correct way to access receipt data is to try accessing the receipt based on success completion handler callback of your IAP request. Once you submit the IAP request there is a server side process which takes care of processing the IAP and then a callback handler from IAP SKPaymentTransactionObserver class is triggered. Using the notification handler from this class you can send the update to your ViewController to check for receipt data.
While further researching, I have found the following article, which solved the problem
article
Important: If you mistakenly use a sandbox tester account to log in to a production environment on your test device instead of your test environment, the sandbox account becomes invalid and can’t be used again. If this happens, create a new sandbox tester account with a new email address.

Error saving CKRecord and CKShare in CKModifyRecordsOperation

I'm trying to save a CKRecord (already created and saved to the database) and a new CKShare to it but I keep getting this error: Optional("Failed to modify some records"). Here is my code:
let csc = UICloudSharingController { controller, preparationCompletionHandler in
let share = CKShare(rootRecord: user)
share[CKShareTitleKey] = "My Share" as CKRecordValue
share.publicPermission = .readWrite
let mro = CKModifyRecordsOperation(recordsToSave: [user, share], recordIDsToDelete: nil)
mro.timeoutIntervalForRequest = 10
mro.timeoutIntervalForResource = 10
mro.modifyRecordsCompletionBlock = { records, recordIDs, error in
if error != nil {
print("ERROR IN MODIFY RECORDS COMPLETION BLOCK\n")
print(error?.localizedDescription)
}
preparationCompletionHandler(share,CKContainer.default(), error)
}
privateData.add(mro)
}
csc.availablePermissions = [.allowPrivate,.allowReadWrite]
self.present(csc, animated:true)
}
The problem is in this method: modifyRecordsCompletionBlock. Can somebody explain me why this is happening?
Thanks in advance!
I got it
All you have to do is create a private custom CKZone and save your CKRecord and CKShare in it, you cannot use the default zone cloud kit gives to you!
To leave a shared record, you have to delete the CKShare record from the shared database otherwise you'll get errors.