Skip to content
Merged
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.8.3...main)
* _Contributing to this repo? Add info about your change here to be included in the next release_

__Fixes__
- Fixed a bug in LiveQuery that prevented reconnecting after a connection was closed. Also added a sendPing method to LiveQuery ([#172](https://github.com/parse-community/Parse-Swift/pull/172)), thanks to [Corey Baker](https://github.com/cbaker6).

### 1.8.3
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.8.2...1.8.3)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
import PlaygroundSupport
import Foundation
import ParseSwift
#if canImport(SwiftUI)
import SwiftUI
#if canImport(Combine)
import Combine
#endif
#endif
PlaygroundPage.current.needsIndefiniteExecution = true

initializeParse()
Expand Down Expand Up @@ -38,8 +43,9 @@ struct GameScore: ParseObject {
//: Be sure you have LiveQuery enabled on your server.

//: Create a query just as you normally would.
var query = GameScore.query("score" > 9)
var query = GameScore.query("score" < 11)

#if canImport(SwiftUI)
//: To use subscriptions inside of SwiftUI
struct ContentView: View {

Expand All @@ -55,7 +61,7 @@ struct ContentView: View {
Text("Unsubscribed from query!")
} else if let event = subscription.event {

//: This is how you register to receive notificaitons of events related to your LiveQuery.
//: This is how you register to receive notifications of events related to your LiveQuery.
switch event.event {

case .entered(let object):
Expand Down Expand Up @@ -93,6 +99,7 @@ struct ContentView: View {
}

PlaygroundPage.current.setLiveView(ContentView())
#endif

//: This is how you subscribe to your created query using callbacks.
let subscription = query.subscribeCallback!
Expand All @@ -109,7 +116,7 @@ subscription.handleSubscribe { subscribedQuery, isNew in
}
}

//: This is how you register to receive notificaitons of events related to your LiveQuery.
//: This is how you register to receive notifications of events related to your LiveQuery.
subscription.handleEvent { _, event in
switch event {

Expand All @@ -126,10 +133,19 @@ subscription.handleEvent { _, event in
}
}

//: Ping the LiveQuery server
ParseLiveQuery.client?.sendPing { error in
if let error = error {
print("Error pinging LiveQuery server: \(error)")
} else {
print("Successfully pinged server!")
}
}

//: Now go to your dashboard, go to the GameScore table and add, update or remove rows.
//: You should receive notifications for each.

//: This is how you register to receive notificaitons about being unsubscribed.
//: This is how you register to receive notifications about being unsubscribed.
subscription.handleUnsubscribe { query in
print("Unsubscribed from \(query)")
}
Expand All @@ -141,6 +157,16 @@ do {
print(error)
}

//: Ping the LiveQuery server. This should produce an error
//: because LiveQuery is disconnected.
ParseLiveQuery.client?.sendPing { error in
if let error = error {
print("Error pinging LiveQuery server: \(error)")
} else {
print("Successfully pinged server!")
}
}

//: Create a new query.
var query2 = GameScore.query("score" > 50)

Expand Down Expand Up @@ -177,11 +203,86 @@ subscription2.handleEvent { _, event in
}
}

//: Now go to your dashboard, go to the GameScore table and add, update or remove rows.
//: You should receive notifications for each, but only with your fields information.
//: To close the current LiveQuery connection.
ParseLiveQuery.client?.close()

//: To close all LiveQuery connections use:
//ParseLiveQuery.client?.closeAll()

//: Ping the LiveQuery server. This should produce an error
//: because LiveQuery is disconnected.
ParseLiveQuery.client?.sendPing { error in
if let error = error {
print("Error pinging LiveQuery server: \(error)")
} else {
print("Successfully pinged server!")
}
}

//: Subscribe to your new query.
let subscription3 = query2.subscribeCallback!

//: As before, setup your subscription and event handlers.
subscription3.handleSubscribe { subscribedQuery, isNew in

//: You can check this subscription is for this query.
if isNew {
print("Successfully subscribed to new query \(subscribedQuery)")
} else {
print("Successfully updated subscription to new query \(subscribedQuery)")
}
}

subscription3.handleEvent { _, event in
switch event {

case .entered(let object):
print("Entered: \(object)")
case .left(let object):
print("Left: \(object)")
case .created(let object):
print("Created: \(object)")
case .updated(let object):
print("Updated: \(object)")
case .deleted(let object):
print("Deleted: \(object)")
}
}

//: Now lets subscribe to an additional query.
let subscription4 = query.subscribeCallback!

//: This is how you receive notifications about the success
//: of your subscription.
subscription4.handleSubscribe { subscribedQuery, isNew in

//: You can check this subscription is for this query
if isNew {
print("Successfully subscribed to new query \(subscribedQuery)")
} else {
print("Successfully updated subscription to new query \(subscribedQuery)")
}
}

//: This is how you register to receive notifications of events related to your LiveQuery.
subscription4.handleEvent { _, event in
switch event {

case .entered(let object):
print("Entered: \(object)")
case .left(let object):
print("Left: \(object)")
case .created(let object):
print("Created: \(object)")
case .updated(let object):
print("Updated: \(object)")
case .deleted(let object):
print("Deleted: \(object)")
}
}

//: This is how you register to receive notificaitons about being unsubscribed.
subscription2.handleUnsubscribe { query in
//: Now we will will unsubscribe from one of the subsriptions, but maintain the connection.
subscription3.handleUnsubscribe { query in
print("Unsubscribed from \(query)")
}

Expand All @@ -192,5 +293,14 @@ do {
print(error)
}

//: Ping the LiveQuery server
ParseLiveQuery.client?.sendPing { error in
if let error = error {
print("Error pinging LiveQuery server: \(error)")
} else {
print("Successfully pinged server!")
}
}

PlaygroundPage.current.finishExecution()
//: [Next](@next)
20 changes: 19 additions & 1 deletion ParseSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,13 @@
91678706259BC5D400BB5B4E /* ParseCloudTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 916786EF259BC59600BB5B4E /* ParseCloudTests.swift */; };
91678710259BC5D600BB5B4E /* ParseCloudTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 916786EF259BC59600BB5B4E /* ParseCloudTests.swift */; };
9167871A259BC5D600BB5B4E /* ParseCloudTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 916786EF259BC59600BB5B4E /* ParseCloudTests.swift */; };
918CED592684C74000CFDC83 /* ParseLiveQuery+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 918CED582684C74000CFDC83 /* ParseLiveQuery+combine.swift */; };
918CED5A2684C74000CFDC83 /* ParseLiveQuery+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 918CED582684C74000CFDC83 /* ParseLiveQuery+combine.swift */; };
918CED5B2684C74000CFDC83 /* ParseLiveQuery+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 918CED582684C74000CFDC83 /* ParseLiveQuery+combine.swift */; };
918CED5C2684C74000CFDC83 /* ParseLiveQuery+combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 918CED582684C74000CFDC83 /* ParseLiveQuery+combine.swift */; };
918CED5E268618C600CFDC83 /* ParseLiveQueryCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 918CED5D268618C600CFDC83 /* ParseLiveQueryCombineTests.swift */; };
918CED5F268618C600CFDC83 /* ParseLiveQueryCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 918CED5D268618C600CFDC83 /* ParseLiveQueryCombineTests.swift */; };
918CED60268618C600CFDC83 /* ParseLiveQueryCombineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 918CED5D268618C600CFDC83 /* ParseLiveQueryCombineTests.swift */; };
9194657824F16E330070296B /* ParseACLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9194657724F16E330070296B /* ParseACLTests.swift */; };
91B40651267A66ED00B129CD /* ParseErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91B40650267A66ED00B129CD /* ParseErrorTests.swift */; };
91B40652267A66ED00B129CD /* ParseErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 91B40650267A66ED00B129CD /* ParseErrorTests.swift */; };
Expand Down Expand Up @@ -707,6 +714,8 @@
9158916A256A07DD0024BE9A /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
916786E1259B7DDA00BB5B4E /* ParseCloud.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseCloud.swift; sourceTree = "<group>"; };
916786EF259BC59600BB5B4E /* ParseCloudTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseCloudTests.swift; sourceTree = "<group>"; };
918CED582684C74000CFDC83 /* ParseLiveQuery+combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseLiveQuery+combine.swift"; sourceTree = "<group>"; };
918CED5D268618C600CFDC83 /* ParseLiveQueryCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseLiveQueryCombineTests.swift; sourceTree = "<group>"; };
9194657724F16E330070296B /* ParseACLTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseACLTests.swift; sourceTree = "<group>"; };
91B40650267A66ED00B129CD /* ParseErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseErrorTests.swift; sourceTree = "<group>"; };
91CB9536265966DF0043E5D6 /* ParseAnanlyticsCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAnanlyticsCombineTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -888,6 +897,7 @@
70110D5B2506ED0E0091CC1D /* ParseInstallationTests.swift */,
70386A5B25D9A4010048EC1B /* ParseLDAPCombineTests.swift */,
70386A4525D99C8B0048EC1B /* ParseLDAPTests.swift */,
918CED5D268618C600CFDC83 /* ParseLiveQueryCombineTests.swift */,
7003963A25A288100052CB31 /* ParseLiveQueryTests.swift */,
70C7DC2024D20F190050419B /* ParseObjectBatchTests.swift */,
7044C1DE25C5C70D0011F6E7 /* ParseObjectCombine.swift */,
Expand Down Expand Up @@ -1044,10 +1054,11 @@
isa = PBXGroup;
children = (
70510AAB259EE25E00FEA700 /* LiveQuerySocket.swift */,
70C5655825AA147B00BDD57F /* ParseLiveQueryConstants.swift */,
7003959425A10DFC0052CB31 /* Messages.swift */,
700395A225A119430052CB31 /* Operations.swift */,
7003960825A184EF0052CB31 /* ParseLiveQuery.swift */,
918CED582684C74000CFDC83 /* ParseLiveQuery+combine.swift */,
70C5655825AA147B00BDD57F /* ParseLiveQueryConstants.swift */,
700395B925A1470F0052CB31 /* Subscription.swift */,
705D950725BE4C08003EF6F8 /* SubscriptionCallback.swift */,
700395DE25A147C40052CB31 /* Protocols */,
Expand Down Expand Up @@ -1651,6 +1662,7 @@
7044C1C825C5B2B10011F6E7 /* ParseAuthentication+combine.swift in Sources */,
707A3BF125B0A4F0000D215C /* ParseAuthentication.swift in Sources */,
70D1BE7325BB43EB00A42E7C /* BaseConfig.swift in Sources */,
918CED592684C74000CFDC83 /* ParseLiveQuery+combine.swift in Sources */,
70386A0625D9718C0048EC1B /* Data+hexString.swift in Sources */,
F97B465F24D9C7B500F4A88B /* KeychainStore.swift in Sources */,
70170A442656B02D0070C905 /* ParseAnalytics.swift in Sources */,
Expand Down Expand Up @@ -1735,6 +1747,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
918CED5E268618C600CFDC83 /* ParseLiveQueryCombineTests.swift in Sources */,
911DB13624C4FC100027F3C7 /* ParseObjectTests.swift in Sources */,
70E09E1C262F0634002DD451 /* ParsePointerCombineTests.swift in Sources */,
89899D592603CF3E002E2043 /* ParseTwitterTests.swift in Sources */,
Expand Down Expand Up @@ -1805,6 +1818,7 @@
7044C1C925C5B2B10011F6E7 /* ParseAuthentication+combine.swift in Sources */,
707A3BF225B0A4F0000D215C /* ParseAuthentication.swift in Sources */,
70D1BE7425BB43EB00A42E7C /* BaseConfig.swift in Sources */,
918CED5A2684C74000CFDC83 /* ParseLiveQuery+combine.swift in Sources */,
70386A0725D9718C0048EC1B /* Data+hexString.swift in Sources */,
F97B466024D9C7B500F4A88B /* KeychainStore.swift in Sources */,
70170A452656B02D0070C905 /* ParseAnalytics.swift in Sources */,
Expand Down Expand Up @@ -1898,6 +1912,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
918CED60268618C600CFDC83 /* ParseLiveQueryCombineTests.swift in Sources */,
709B98512556ECAA00507778 /* ParseEncoderExtraTests.swift in Sources */,
70E09E1E262F0634002DD451 /* ParsePointerCombineTests.swift in Sources */,
89899D642603CF3F002E2043 /* ParseTwitterTests.swift in Sources */,
Expand Down Expand Up @@ -1960,6 +1975,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
918CED5F268618C600CFDC83 /* ParseLiveQueryCombineTests.swift in Sources */,
70F2E2B6254F283000B2EA5C /* ParseACLTests.swift in Sources */,
70E09E1D262F0634002DD451 /* ParsePointerCombineTests.swift in Sources */,
89899D632603CF3E002E2043 /* ParseTwitterTests.swift in Sources */,
Expand Down Expand Up @@ -2030,6 +2046,7 @@
7044C1CB25C5B2B10011F6E7 /* ParseAuthentication+combine.swift in Sources */,
707A3BF425B0A4F0000D215C /* ParseAuthentication.swift in Sources */,
70D1BE7625BB43EB00A42E7C /* BaseConfig.swift in Sources */,
918CED5C2684C74000CFDC83 /* ParseLiveQuery+combine.swift in Sources */,
70386A0925D9718C0048EC1B /* Data+hexString.swift in Sources */,
F97B460524D9C6F200F4A88B /* NoBody.swift in Sources */,
70170A472656B02D0070C905 /* ParseAnalytics.swift in Sources */,
Expand Down Expand Up @@ -2122,6 +2139,7 @@
7044C1CA25C5B2B10011F6E7 /* ParseAuthentication+combine.swift in Sources */,
707A3BF325B0A4F0000D215C /* ParseAuthentication.swift in Sources */,
70D1BE7525BB43EB00A42E7C /* BaseConfig.swift in Sources */,
918CED5B2684C74000CFDC83 /* ParseLiveQuery+combine.swift in Sources */,
70386A0825D9718C0048EC1B /* Data+hexString.swift in Sources */,
F97B460424D9C6F200F4A88B /* NoBody.swift in Sources */,
70170A462656B02D0070C905 /* ParseAnalytics.swift in Sources */,
Expand Down
12 changes: 12 additions & 0 deletions Sources/ParseSwift/LiveQuery/LiveQuerySocket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ extension LiveQuerySocket {
.encode(StandardMessage(operation: .connect,
additionalProperties: true))
guard let encodedAsString = String(data: encoded, encoding: .utf8) else {
let error = ParseError(code: .unknownError,
message: "Couldn't encode connect message: \(encoded)")
completion(error)
return
}
task.send(.string(encodedAsString)) { error in
Expand Down Expand Up @@ -104,6 +107,15 @@ extension LiveQuerySocket {
}
}

// MARK: Ping
@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
extension LiveQuerySocket {

func sendPing(_ task: URLSessionWebSocketTask, pongReceiveHandler: @escaping (Error?) -> Void) {
task.sendPing(pongReceiveHandler: pongReceiveHandler)
}
}

// MARK: URLSession
@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
extension URLSession {
Expand Down
51 changes: 51 additions & 0 deletions Sources/ParseSwift/LiveQuery/ParseLiveQuery+combine.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// ParseLiveQuery+combine.swift
// ParseSwift
//
// Created by Corey Baker on 6/24/21.
// Copyright © 2021 Parse Community. All rights reserved.
//

#if canImport(Combine)
import Foundation
import Combine

@available(macOS 10.15, iOS 13.0, macCatalyst 13.0, watchOS 6.0, tvOS 13.0, *)
extension ParseLiveQuery {
// MARK: Functions - Combine

/**
Manually establish a connection to the `ParseLiveQuery` Server. Publishes when established.
- parameter isUserWantsToConnect: Specifies if the user is calling this function. Defaults to `true`.
- returns: A publisher that eventually produces a single value and then finishes or fails.
*/
public func openPublisher(isUserWantsToConnect: Bool = true) -> Future<Void, Error> {
Future { promise in
self.open(isUserWantsToConnect: isUserWantsToConnect) { error in
guard let error = error else {
promise(.success(()))
return
}
promise(.failure(error))
}
}
}

/**
Sends a ping frame from the client side. Publishes when a pong is received from the
server endpoint.
- returns: A publisher that eventually produces a single value and then finishes or fails.
*/
public func sendPingPublisher() -> Future<Void, Error> {
Future { promise in
self.sendPing { error in
guard let error = error else {
promise(.success(()))
return
}
promise(.failure(error))
}
}
}
}
#endif
Loading