ParseLiveQuery-iOS-OSX icon indicating copy to clipboard operation
ParseLiveQuery-iOS-OSX copied to clipboard

Emitted Events from WebSocket pings not forwarded to Subscription Handler

Open HackShitUp opened this issue 2 years ago • 4 comments

New Issue Checklist

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
  • 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

HackShitUp avatar Feb 03 '23 07:02 HackShitUp

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

432player avatar May 22 '23 12:05 432player

Does anyone want to open a PR and propose a solution?

mtrezza avatar May 22 '23 14:05 mtrezza

@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?

mtrezza avatar Jun 13 '23 07:06 mtrezza