Emitted Events from WebSocket pings not forwarded to Subscription Handler
New Issue Checklist
- [X] I am not disclosing a vulnerability.
- [X] I am not just asking a question.
- [X] I have searched through existing issues.
- [X] I can reproduce the issue with the latest version of Parse Server and the Parse LiveQuery ObjC SDK.
Issue Description
subscription?.handleEvent({ (query: PFQuery<PFObject>, event: Event<PFObject>) in
print("Fired: \(#line)")
})
The Subscription.handleEvent protocol never gets called but WebSocket pings are received (logged in Client.shouldPrintWebSocketTrace)
Steps to reproduce
Define the client, and subscribe to a query the standard way:
let query = PFQuery(className: "Test")
query.whereKey("objectId", equalTo: "objectId")
client = ParseLiveQuery.Client(server: serverURL)
client?.shouldPrintWebSocketLog = true
client?.shouldPrintWebSocketTrace = true
subscription = client?.subscribe(query)
subscription?.handleEvent({ (query: PFQuery<PFObject>, event: Event<PFObject>) in
print("Fired: \(#line)")
})
WebSocket trace logs received ping but does not forward to the protocol.
Actual Outcome
Event not forwarded at the ParseLiveQuery layer
Expected Outcome
All emitted events should be accessible by the Subscription's handler
Environment
Parse LiveQuery ObjC SDK
- SDK version:
2.8.1- Bolts-Swift:
1.5.0 - Parse:
1.19.0
- Bolts-Swift:
- Operating system version:
iOS 16.0
Server
- Parse Server version:
5.4.1 - Local or remote host (AWS, Azure, Google Cloud, Heroku, Digital Ocean, etc):
Remote/Local
Thanks for opening this issue!
- 🚀 You can help us to fix this issue faster by opening a pull request with a failing test. See our Contribution Guide for how to make a pull request, or read our New Contributor's Guide if this is your first time contributing.
I had the same issue. Server Side: "parse": "4.0.1", "parse-server": "^6.0.0"
IOS Client Side:
- Parse (1.19.4):
- Parse/Core (= 1.19.4)
- Parse/Core (1.19.4):
- Bolts/Tasks (= 1.9.1)
- ParseLiveQuery (2.8.1):
- Bolts-Swift (~> 1.5.0)
- Starscream (~> 4.0.4)
Apparently from some version of parse-server the received message from the ParseLiveQuery didn't return with the type on the main object in the string received. for example: { "op": "update", "clientId": "559ca4bd-7425-4ea8-b7ed-8ac73e6c6d15", "requestId": 2, "object": { "user1": { "__type": "Pointer", "className": "_User", "objectId": "8YoD1R8RTJ" }, "user2": { "__type": "Pointer", "className": "_User", "objectId": "DrvZzHZASt" }, "createdAt": "2023-05-16T09:57:29.412Z", "updatedAt": "2023-05-20T20:13:46.736Z", "messages": { "__type": "Relation", "className": "Message" }, "className": "PrivateChat", "objectId": "lsxP3br9EG" }, "original": { "user1": { "__type": "Pointer", "className": "_User", "objectId": "8YoD1R8RTJ" }, "user2": { "__type": "Pointer", "className": "_User", "objectId": "DrvZzHZASt" }, "createdAt": "2023-05-16T09:57:29.412Z", "updatedAt": "2023-05-20T20:12:07.365Z", "messages": { "__type": "Relation", "className": "Message" }, "className": "PrivateChat", "objectId": "lsxP3br9EG" } } As you can see "object" has no type so parse doesn't know how to parse it into a PFObject. that's why it says something like "bad json" when serializing it into PFObject.
So I've added a short code to always add a __type = "Object" into the Json for the first main "object" and I removed the original object, I don't see how original object is supported in the ParseLiveQuery-iOS-OSX library.
So the received json would now look like this: { "op": "update", "clientId": "559ca4bd-7425-4ea8-b7ed-8ac73e6c6d15", "requestId": 2, "object": { "user1": { "__type": "Pointer", "className": "_User", "objectId": "8YoD1R8RTJ" }, "user2": { "__type": "Pointer", "className": "_User", "objectId": "DrvZzHZASt" }, "createdAt": "2023-05-16T09:57:29.412Z", "updatedAt": "2023-05-20T20:13:46.736Z", "messages": { "__type": "Relation", "className": "Message" }, "__type": "Object", //This is what I've added, and removed "original" key from the json. "className": "PrivateChat", "objectId": "lsxP3br9EG" } }
So in the function func handleOperationAsync(_ string: String) -> Task<Void> in ClientPrivate.swift I've changed the code to look like this: func handleOperationAsync(_ string: String) -> Task<Void> { return Task(.queue(queue)) { if self.shouldPrintWebSocketTrace { NSLog("ParseLiveQuery: Received message: (string)") } guard let jsonData = string.data(using: .utf8), let jsonDecoded = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: AnyObject] else { throw LiveQueryErrors.InvalidResponseError(response: string) }
//--------START CHANGED CODE HERE------// var modifiedJson = jsonDecoded // Remove the "original" key if present modifiedJson.removeValue(forKey: "original")
if let object = modifiedJson["object"] as? [String: AnyObject] {
modifiedJson["object"] = addTypeToObject(object) as AnyObject
}
guard let response: ServerResponse = try? ServerResponse(json: modifiedJson) else {
throw LiveQueryErrors.InvalidResponseError(response: string)
}
func addTypeToObject(_ object: [String: AnyObject]) -> [String: AnyObject] {
var modifiedObject = object
for (key, value) in modifiedObject {
if key == "className" && !(value is [String: AnyObject]) {
modifiedObject["__type"] = "Object" as AnyObject
break
}
}
return modifiedObject
}
//--------END CHANGED CODE HERE------// switch response { case .connected: let sessionToken = PFUser.current()?.sessionToken self.subscriptions.forEach { _ = self.sendOperationAsync(.subscribe(requestId: $0.requestId, query: $0.query, sessionToken: sessionToken)) }
case .redirect:
// TODO: Handle redirect.
break
case .subscribed(let requestId):
self.subscriptionRecord(requestId)?.subscribeHandlerClosure(self)
case .unsubscribed(let requestId):
guard
let recordIndex = self.subscriptions.firstIndex(where: { $0.requestId == requestId })
else {
break
}
let record: SubscriptionRecord = self.subscriptions[recordIndex]
record.unsubscribeHandlerClosure(self)
self.subscriptions.remove(at: recordIndex)
case .create, .delete, .enter, .leave, .update:
var requestId: RequestId = RequestId(value: 0)
guard
let event: Event<PFObject> = try? Event(serverResponse: response, requestId: &requestId),
let record = self.subscriptionRecord(requestId)
else {
break
}
record.eventHandlerClosure(event, self)
case .error(let requestId, let code, let error, let reconnect):
let error = LiveQueryErrors.ServerReportedError(code: code, error: error, reconnect: reconnect)
if let requestId = requestId {
self.subscriptionRecord(requestId)?.errorHandlerClosure(error, self)
} else {
throw error
}
}
}
}
I hope that this will be taken care of by the library itself cause if I update the library I will always have to remember to add this code. Perhaps if there's is no type assume that it is "__type" = "Object" or something if it's missing, not really sure what the best way would be. but I would appreciate it if this can be fixed internally.
Much blessings
Does anyone want to open a PR and propose a solution?
@432player, @HackShitUp The LiveQuery feature has been added as a module to the Parse Apple SDK; could you please verify that this issue is fixed there, and if not open a new issue there?