Code Monkey home page Code Monkey logo

parse-swift's Introduction

parse-repository-header-sdk-swift

iOS · macOS · watchOS · tvOS · Linux · Android · Windows


Build Status CI Build Status Release Coverage Carthage Pod

Swift Versions Platforms

Backers on Open Collective Sponsors on Open Collective License Forum Twitter


A pure Swift library that gives you access to the powerful Parse Server backend from your Swift applications.

For more information about the Parse Platform and its features, see the public documentation. The ParseSwift SDK is not a port of the Parse-SDK-iOS-OSX SDK and though some of it may feel familiar, it is not backwards compatible and is designed using protocol oriented programming (POP) and value types instead of OOP and reference types. You can learn more about POP by watching Protocol-Oriented Programming in Swift or Protocol and Value Oriented Programming in UIKit Apps videos from previous WWDC's. For more details about ParseSwift, visit the api documentation.

To learn how to use or experiment with ParseSwift, you can run and edit the ParseSwift.playground. You can use the parse-server in this repo which has docker compose files (docker-compose up gives you a working server) configured to connect with the playground files, has Parse Dashboard, and can be used with MongoDB or PostgreSQL. You can also configure the Swift Playgrounds to work with your own Parse Server by editing the configuation in Common.swift. To learn more, check out CONTRIBUTING.md.


Installation

You can use The Swift Package Manager (SPM) to install ParseSwift by adding the following description to your Package.swift file:

// swift-tools-version:5.5
import PackageDescription

let package = Package(
    name: "YOUR_PROJECT_NAME",
    dependencies: [
        .package(url: "https://github.com/parse-community/Parse-Swift", .upToNextMajor(from: "4.0.0")),
    ]
)

Then run swift build.

You can also install using SPM in your Xcode project by going to "Project->NameOfYourProject->Swift Packages" and placing https://github.com/parse-community/Parse-Swift.git in the search field.

Add the following line to your Podfile:

pod 'ParseSwift'

Run pod install, and you should now have the latest version from the main branch.

Add the following line to your Cartfile:

github "parse-community/Parse-Swift"

Run carthage update, and you should now have the latest version of ParseSwift SDK in your Carthage folder.

Usage Guide

After installing ParseSwift, to use it first import ParseSwift in your AppDelegate.swift and then add the following code in your application:didFinishLaunchingWithOptions: method:

ParseSwift.initialize(applicationId: "xxxxxxxxxx", clientKey: "xxxxxxxxxx", serverURL: URL(string: "https://example.com")!)

Please checkout the Swift Playground for more usage information.

LiveQuery

Query is one of the key concepts on the Parse Platform. It allows you to retrieve ParseObjects by specifying some conditions, making it easy to build apps such as a dashboard, a todo list or even some strategy games. However, Query is based on a pull model, which is not suitable for apps that need real-time support.

Suppose you are building an app that allows multiple users to edit the same file at the same time. Query would not be an ideal tool since you can not know when to query from the server to get the updates.

To solve this problem, we introduce Parse LiveQuery. This tool allows you to subscribe to a Query you are interested in. Once subscribed, the server will notify clients whenever a ParseObject that matches the Query is created or updated, in real-time.

Setup Server

Parse LiveQuery contains two parts, the LiveQuery server and the LiveQuery clients (this SDK). In order to use live queries, you need to at least setup the server.

The easiest way to setup the LiveQuery server is to make it run with the Open Source Parse Server.

Use Client

SwiftUI View Models Using Combine

The LiveQuery client interface is based around the concept of Subscriptions. You can register any Query for live updates from the associated live query server and use the query as a view model for a SwiftUI view by simply using the subscribe property of a query:

let myQuery = GameScore.query("points" > 9)

struct ContentView: View {

    //: A LiveQuery subscription can be used as a view model in SwiftUI
    @StateObject var subscription = myQuery.subscribe!
    
    var body: some View {
        VStack {

            if subscription.subscribed != nil {
                Text("Subscribed to query!")
            } else if subscription.unsubscribed != nil {
                Text("Unsubscribed from query!")
            } else if let event = subscription.event {

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

                case .entered(let object):
                    Text("Entered with points: \(object.points)")
                case .left(let object):
                    Text("Left with points: \(object.points)")
                case .created(let object):
                    Text("Created with points: \(object.points)")
                case .updated(let object):
                    Text("Updated with points: \(object.points)")
                case .deleted(let object):
                    Text("Deleted with points: \(object.points)")
                }
            } else {
                Text("Not subscribed to a query")
            }

            Spacer()

            Text("Update GameScore in Parse Dashboard to see changes here")

            Button(action: {
                try? query.unsubscribe()
            }, label: {
                Text("Unsubscribe")
                    .font(.headline)
                    .background(Color.red)
                    .foregroundColor(.white)
                    .padding()
                    .cornerRadius(20.0)
                    .frame(width: 300, height: 50)
            })
        }
    }
}

or by calling the subscribe(_ client: ParseLiveQuery) method of a query. If you want to customize your view model more you can subclass Subscription or add the subscription to your own view model. You can test out LiveQuery subscriptions in Swift Playgrounds.

Traditional Callbacks

You can also use asynchronous call backs to subscribe to a LiveQuery:

let myQuery = Message.query("from" == "parse")
guard let subscription = myQuery.subscribeCallback else {
    print("Error subscribing...")
    return
}

or by calling the subscribeCallback(_ client: ParseLiveQuery) method of a query.

Where Message is a ParseObject.

Once you've subscribed to a query, you can handle events on them, like so:

subscription.handleSubscribe { subscribedQuery, isNew in

    //Handle the subscription however you like.
    if isNew {
        print("Successfully subscribed to new query \(subscribedQuery)")
    } else {
        print("Successfully updated subscription to new query \(subscribedQuery)")
    }
}

You can handle any event listed in the LiveQuery spec:

subscription.handleEvent { _, event in
    // Called whenever an object was created
    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)")
    }
}

Similiarly, you can unsubscribe and register to be notified when it occurs:

subscription.handleUnsubscribe { query in
    print("Unsubscribed from \(query)")
}

//: To unsubscribe from your query.
do {
    try query.unsubscribe()
} catch {
    print(error)
}

Handling errors is and other events is similar, take a look at the Subscription class for more information. You can test out LiveQuery subscriptions in Swift Playgrounds.

Advanced Usage

You are not limited to a single Live Query Client - you can create multiple instances of ParseLiveQuery, use certificate authentication and pinning, receive metrics about each client connection, connect to individual server URLs, and more.

Migration from Parse ObjC SDK

See the Migration Guide to help you migrate from the Parse ObjC SDK.

parse-swift's People

Contributors

abs8090 avatar cbaker6 avatar dblythy avatar dependabot[bot] avatar finestructure avatar flovilmart avatar jaysonng avatar jt9897253 avatar lsmilek1 avatar mtrezza avatar pmmlo avatar pranjalsatija avatar rocxteady avatar snyk-bot avatar tomwfox avatar vdkdamian avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

parse-swift's Issues

Evolution?

@flovilmart I'm really interested in helping to make this a thing, and I'd love to take on some tasks (major ones, too!) to get this up to feature parity with the Objective C version. Are there any evolution guidelines, or a list of long and short term goals and features? If so, could you please point me to them so I can begin to get to try implementing some of the features that are planned? If not, could you just give me some general advice on some of the things that are planned? I'd love to have an active role in the development of this framework.

Array of Pointer not working in User class

Hey Parse-Community!

I'm trying to get an array of pointers from a user.
This works perfectly with a logged in user.

User.current?.fetch(includeKeys: ["pointerArray"], options: [], callbackQueue: .main, completion: { result in
    // Do stuff
}

But when logging in for the first time it doesn't work.

User.login(username: username, password: password) { results in
   // Do stuff
}

I get the following error:

ParseError(code: ParseSwift.ParseError.Code.unknownError, message: "Error decoding parse-server response: {...} with error: The data couldn’t be read because it is missing. Format: {...} 

The Objects:

struct User: ParseUser, Codable {
    var username: String?
    var email: String?
    var password: String?
    var authData: [String: [String: String]?]?
    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?

    var pointerArray: [CustomObject]?
}

struct CustomObject: ParseObject, Codable {
    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?

    var title: String?
}

Help wanted with v1.0 feature additions

We are gearing up for the v1.0 release and would like your assistance with adding features. The features to be added can be found in https://github.com/parse-community/Parse-Swift/projects/1

Many of the features to be added have counterparts in the Objective-C SDK and though they are not implemented the same, can provide some guidance on what's needed. The Objective-C SDK test cases are also useful understanding what needs to be tested for the features.

There are some examples of Parse-Swift unit tests here

If you decide to take one on, just submit a Pull Request and change it to "Draft" to let people know you are working on it.

Custom objectId before saving

Hello,

is there a way how to set custom objectId before the object gets saved into Parse? This does not need to be for user login/sign-up, bot than later for other objects. The reason is that I am allowing user to save objects locally in the app (Realm) before they need or are synced with server, so the Id is already defined.

After login the user I tried following object:

`struct TestParseObject: ParseObject {

var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
var itemName: String?

}`

saving with this code:

static func testSave() { var testObject = TestParseObject() testObject.itemName = "Able" testObject.objectId = "EaGax1Mnym" testObject.save { (result) in print("save result: \(result)") } }

result is:
save result: failure(ParseSwift.ParseError(code: ParseSwift.ParseError.Code.objectNotFound, message: "Object not found."))

If I do not change objectId, the object saves successfully.

Workaround would be to add one more field and save the other Id in that separate field, but that feels like unnecessary traffic and server load. In the custom server setting I was able to set:

{ "allowCustomObjectId": true }

And I noticed through the community that above functionality should be possible. Looking into the code description I see that there might be some wrong password also:

/** Object doesn't exist, or has an incorrect password. */ case objectNotFound = 101

But the password reference is only for user object, right?

Saving ParseObjects with File properties

Right now I am trying to save an ParseObject with a File (ParseFile) property as this:

    struct TestClass: ParseObject {
        var objectId: String?
        var createdAt: Date?
        var updatedAt: Date?
        var ACL: ParseACL?
        var location: GeoPoint?
    
        var name: String
        var age: Int
        var photo: File
    
        init(name: String, age: Int) {
            self.name = name
            self.age = age
        }
    }

It gives me two errors:
Type 'TestClass' does not conform to protocol 'Equatable'
Type 'TestClass' does not conform to protocol 'Hashable'

The first one I can fix by:
static func == (lhs: TestClass, rhs: TestClass) -> Bool { return lhs.objectId == rhs.objectId }

How exactly can I save such a property?

Cloud Function with authenticated user validation

I have a cloud function that requires advanced validation requiresUser.

e.g.

Parse.Cloud.define("averageStars", async (request) => {
  const query = new Parse.Query("Review");
  query.equalTo("movie", request.params.movie);
  const results = await query.find();
  let sum = 0;
  for (let i = 0; i < results.length; ++i) {
    sum += results[i].get("stars");
  }
  return sum / results.length;
},{
  fields : ['movie'],
  requireUser: true
});

I played around with API.Option.tags to try to pass request.user in cloud.runFunction(options: [API.Option.tags(["user": User.current.objectId])]) {... but it seems I would have to manually encode a json string (because .tags has type [String:String]). I continue to get the error: Cloud error: ParseError(code: ParseSwift.ParseError.Code.validationError, message: "Validation failed. Please login to continue.")

Is there a way that I am missing to pass the locally authenticated User object as request.user to the cloud function for successful authenticated user validation?

If not, could someone that is familiar with Cloud Function advanced validators potentially add the encoding for API.Option.user or something similar? @cbaker6

Thanks!

passing parameters to Cloud Function

Hi,

I was going through the playgrounds and documentation, I am not sure how to pass parameters to the cloud function. In the ParseCloud object I should define the parameter directly or a dictionary with parameters? for example:

//: Create your own value typed `ParseCloud` type.
struct CloudFunction: ParseCloud {

    //Return type of your Cloud Function
    typealias ReturnType = AnyDecodable//[PrsProfile]
    //These are required for Object
    var functionJobName: String
    
    
    init(type: CloudFunctionType, filter: [Int]? = nil) {
        self.functionJobName = type.rawValue
        self.filter = filter
    }

    //: If your cloud function takes arguments, they can be passed by creating properties:
    var filter: [Int]? = [5, 15, 65]
    var argument1: [String: [Int]?] = ["test": [5, 15, 65]]
}

if I try to access both in the cloud function, the function returns:

ParseError(code: ParseSwift.ParseError.Code.scriptError, message: "Cannot read property 'find' of undefined")

I tried to access it in the cloud code like this:

const test = request.params.argument1["test"];
const bloomFilter = request.params.filter;

Did I missed something? As I never worked with javascript before its hard for me to judge if the syntax of the cloud code is correct. Thank you!

Authentication Delegate

I'm in the process of writing some custom authentication providers for an internal system we have, using the existing ParseLDAP and other 3rd party classes that already exist as examples. We have this working on the Objective-C SDK, but I have a couple questions about the Swift SDK.

The ObjC SDK has an Authentication Delegate with a method with the siguature public func restoreAuthentication(withAuthData authData: [String: String]?) -> Bool which gets called on launch to recreate any state that needs to be done. For example, we use this time to recreate the oAuthSwift client with our token details, and refresh the token if necessary.

I don't see a similar system for this in the Swift SDK. How does auth restoration from serialized data on launch work in the SDK? I can potentially send in a PR if it's something that needs to be added.

.fetch() with custom objectId

I am not sure if I misunderstood fetch and find or I am hitting some bug.

  • fetch I should use when I want to get objects based on objectId field
  • find I should use when I have some query criteria and want to find one or more objects

I believe I can use find to get one single object with given objectId also, but I guess it is more performant to use fetch on unique objects?

What I got stuck is a fetch response returning empty object. I use custom objectId on this object and the save and update is working well. Before the first fetch I don't know the createAt date, so I init the struct with any Date():

        init(objectId: String) {
                self.objectId = objectId
                self.createdAt = Date()
        }

then I call the fetch like this:

        let profileToFetch = PrsProfile(objectId: objectId)
        print("profileToFetch: \(profileToFetch)")
        profileToFetch.fetch { results in
            switch results {
            case .success(let profile):
                print("fetched profile: \(profile)")
            case .failure(let error):
                print("error: \(error)")
            }
        }

So struct being passed to fetch is:

profileToFetch: PrsProfile ({"createdAt":{"__type":"Date","iso":"2021-05-11T09:28:21.877Z"},"objectId":"Uo9eYl2rkB"})

But the response gives back empty object:

fetched profile: PrsProfile ({})

When I do not specify createdAt in init, the fetch function throws error due to isSaved check in SDK:

guard object.isSaved else {
            throw ParseError(code: .unknownError, message: "Cannot Fetch an object without id")
        }

inkludekemys: ["*"] is also not helping here. There is also no code in beforeSave trigger. How should I fetch an object with customId?

Thank you!

Adding Support for more 3rd Party Authentication SDK's

New Feature / Enhancement Checklist

Current Limitation

Parse-Swift currently supports Anonymous authentication (ParseAnonymous) and Apple authentication (see all adapters below).

Microsoft Graph, Instagram, and any Parse Server supported authentication method do work, but they currently don't have helper methods to connect to them as easily as using ParseApple.

Populating authData directly requires knowing the correct key/values for the respective adapter and can quickly lead to mistakes in code during login, linking, etc.

Feature / Enhancement Description

To make the process simpler, developers can easily add support for additional 3rd party authentication methods by using ParseGoogle as a template.

Example Use Case

We are encouraging developers to open PR's (see the contributors guide) to add missing authentication methods by following the process below:

  1. Create a new file inside the 3rd Party folder and name it ParseNAME.swift, for example ParseFacebook .
  2. Copy the code from ParseGoogle.swift, ParseGoogle+async.swift, ParseGoogle+combine.swift into the new respective adapter files you are creating.
  3. Refactor by: a) Find/replace Google -> Name, b) Find/replace google -> name
  4. Modify/tailor the helper methods to work with the specific SDK. This will require modifying AuthenticationKeys in the file.
  5. Add any additional methods that may be needed to verify inputs or simplify passing in values to the method.
  6. Update documentation/comments in the code to reflect the new adapter
  7. Copy/modify the ParseGoogleTests.swift, ParseGoogleCombineTests.swift file to add the respective test cases.
  8. Refactor test files by: a) Find/replace Google -> Name, b) Find/replace google -> name
  9. Submit your pull request for review

Note that the dependency of the the respective framework, i.e. import FBSDKCoreKit shouldn't be part of the PR as Parse-Swift is dependency free. Instead the implementation should just ensure the necessary AuthenticationKeys are captured to properly authenticate the respective framework with a Parse Server. The developer using the respective authentication method is responsible for adding any dependencies for their app. This lets the developer manage and use any version of any authentication SDK's they want, they simply need to specify the required keys that the Parse Server requires.

If you need help, feel free to ask questions here or in your added PR.

Potential adapters (checked one’s are already implemented in Parse-Swift):

  • Apple
  • Facebook
  • Github
  • Google
  • Instagram
  • Janrain Capture
  • Janrain Engage
  • Keycloak
  • LDAP
  • LinkedIn
  • Meetup
  • Microsoft Graph
  • PhantAuth
  • QQ
  • Spotify
  • Twitter
  • vKontakte
  • WeChat
  • Weibo

Alternatives / Workarounds

To authenticate without helper methods, you can use the following available on any ParseUser:

// MARK: 3rd Party Authentication - Login
/**
Makes a *synchronous* request to login a user with specified credentials.
Returns an instance of the successfully logged in `ParseUser`.
This also caches the user locally so that calls to *current* will use the latest logged in user.
- parameter type: The authentication type.
- parameter authData: The data that represents the authentication.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- throws: An error of type `ParseError`.
- returns: An instance of the logged in `ParseUser`.
If login failed due to either an incorrect password or incorrect username, it throws a `ParseError`.
*/
static func login(_ type: String,
authData: [String: String],
options: API.Options) throws -> Self {
let body = SignupLoginBody(authData: [type: authData])
return try signupCommand(body: body).execute(options: options)
}
/**
Makes an *asynchronous* request to log in a user with specified credentials.
Returns an instance of the successfully logged in `ParseUser`.
This also caches the user locally so that calls to *current* will use the latest logged in user.
- parameter type: The authentication type.
- parameter authData: The data that represents the authentication.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- parameter callbackQueue: The queue to return to after completion. Default value of .main.
- parameter completion: The block to execute.
It should have the following argument signature: `(Result<Self, ParseError>)`.
*/
static func login(_ type: String,
authData: [String: String],
options: API.Options,
callbackQueue: DispatchQueue,
completion: @escaping (Result<Self, ParseError>) -> Void) {
let body = SignupLoginBody(authData: [type: authData])
signupCommand(body: body)
.executeAsync(options: options) { result in
callbackQueue.async {
completion(result)
}
}
}
// MARK: 3rd Party Authentication - Link
func isLinked(with type: String) -> Bool {
guard let authData = self.authData?[type] else {
return false
}
return authData != nil
}
func unlink(_ type: String,
options: API.Options,
callbackQueue: DispatchQueue,
completion: @escaping (Result<Self, ParseError>) -> Void) {
guard let current = Self.current,
current.authData != nil else {
let error = ParseError(code: .unknownError, message: "Must be logged in to unlink user")
callbackQueue.async {
completion(.failure(error))
}
return
}
if current.isLinked(with: type) {
guard let authData = current.apple.strip(current).authData else {
let error = ParseError(code: .unknownError, message: "Missing authData.")
callbackQueue.async {
completion(.failure(error))
}
return
}
let body = SignupLoginBody(authData: authData)
current.linkCommand(body: body)
.executeAsync(options: options) { result in
callbackQueue.async {
completion(result)
}
}
} else {
callbackQueue.async {
completion(.success(self))
}
}
}
/**
Makes a *synchronous* request to link a user with specified credentials. The user should already be logged in.
Returns an instance of the successfully linked `ParseUser`.
This also caches the user locally so that calls to *current* will use the latest logged in user.
- parameter type: The authentication type.
- parameter authData: The data that represents the authentication.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- throws: An error of type `ParseError`.
- returns: An instance of the logged in `ParseUser`.
If login failed due to either an incorrect password or incorrect username, it throws a `ParseError`.
*/
static func link(_ type: String,
authData: [String: String],
options: API.Options) throws -> Self {
guard let current = Self.current else {
throw ParseError(code: .unknownError, message: "Must be logged in to link user")
}
let body = SignupLoginBody(authData: [type: authData])
return try current.linkCommand(body: body).execute(options: options)
}
/**
Makes an *asynchronous* request to link a user with specified credentials. The user should already be logged in.
Returns an instance of the successfully linked `ParseUser`.
This also caches the user locally so that calls to *current* will use the latest logged in user.
- parameter type: The authentication type.
- parameter authData: The data that represents the authentication.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- parameter callbackQueue: The queue to return to after completion. Default value of .main.
- parameter completion: The block to execute.
It should have the following argument signature: `(Result<Self, ParseError>)`.
*/
static func link(_ type: String,
authData: [String: String],
options: API.Options,
callbackQueue: DispatchQueue,
completion: @escaping (Result<Self, ParseError>) -> Void) {
guard let current = Self.current else {
let error = ParseError(code: .unknownError, message: "Must be logged in to link user")
callbackQueue.async {
completion(.failure(error))
}
return
}
let body = SignupLoginBody(authData: [type: authData])
current.linkCommand(body: body)
.executeAsync(options: options) { result in
callbackQueue.async {
completion(result)
}
}
}

and here:

// MARK: 3rd Party Authentication - Login Combine
/**
Makes an *asynchronous* request to log in a user with specified credentials.
Publishes an instance of the successfully logged in `ParseUser`.
This also caches the user locally so that calls to *current* will use the latest logged in user.
- parameter type: The authentication type.
- parameter authData: The data that represents the authentication.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- returns: A publisher that eventually produces a single value and then finishes or fails.
*/
static func loginPublisher(_ type: String,
authData: [String: String],
options: API.Options = []) -> Future<Self, ParseError> {
Future { promise in
Self.login(type,
authData: authData,
options: options,
completion: promise)
}
}
/**
Unlink the authentication type *asynchronously*. Publishes when complete.
- parameter type: The type to unlink. The user must be logged in on this device.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- returns: A publisher that eventually produces a single value and then finishes or fails.
*/
func unlinkPublisher(_ type: String,
options: API.Options = []) -> Future<Self, ParseError> {
Future { promise in
self.unlink(type,
options: options,
completion: promise)
}
}
/**
Makes an *asynchronous* request to link a user with specified credentials. The user should already be logged in.
Publishes an instance of the successfully linked `ParseUser`.
This also caches the user locally so that calls to *current* will use the latest logged in user.
- parameter type: The authentication type.
- parameter authData: The data that represents the authentication.
- parameter options: A set of header options sent to the server. Defaults to an empty set.
- returns: A publisher that eventually produces a single value and then finishes or fails.
*/
static func linkPublisher(_ type: String,
authData: [String: String],
options: API.Options = []) -> Future<Self, ParseError> {
Future { promise in
Self.link(type,
authData: authData,
options: options,
completion: promise)
}
}

3rd Party References

Parse decoding error at ParseAuthentication linkCommand when sessionToken is missing from response

I see a parse JsonDecoder error:

ParseError code=-1 error=Error decoding parse-server response: Optional(<NSHTTPURLResponse: 0x6000006e84c0> { URL: https://podlive-parse-server.herokuapp.com/parse/users/jXjF1qjJhw } { Status Code: 200, Headers {
    "Access-Control-Allow-Headers" =     (
        "X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, X-Parse-Request-Id, Content-Type, Pragma, Cache-Control"
    );
    "Access-Control-Allow-Methods" =     (
        "GET,PUT,POST,DELETE,OPTIONS"
    );
    "Access-Control-Allow-Origin" =     (
        "*"
    );
    "Access-Control-Expose-Headers" =     (
        "X-Parse-Job-Status-Id, X-Parse-Push-Status-Id"
    );
    Connection =     (
        "keep-alive"
    );
    "Content-Length" =     (
        40
    );
    "Content-Type" =     (
        "application/json; charset=utf-8"
    );
    Date =     (
        "Thu, 01 Apr 2021 13:24:07 GMT"
    );
    Etag =     (
        "W/\"28-69gFBc5Q54gsdpFK0tjSk36K+Xc\""
    );
    Server =     (
        Cowboy
    );
    Via =     (
        "1.1 vegur"
    );
    "X-Powered-By" =     (
        Express
    );
} }) with error: Die Daten konnten nicht gelesen werden, da sie fehlen. Format: Optional("{\"updatedAt\":\"2021-04-01T13:24:07.647Z\"}")

It says data can not be read because its missing.

I tracked it down to this location in ParseSwift code:

let user = try ParseCoding.jsonDecoder().decode(UpdateSessionTokenResponse.self, from: data)

The data object received from the server looks like this:

"{\"updatedAt\":\"2021-04-01T13:25:40.915Z\"}"

However ParseSwift expects this format:

internal struct UpdateSessionTokenResponse: Decodable {
    var updatedAt: Date
    let sessionToken: String
}

The sessionToken is missing.

This might be caused by using authentication wrong in my code. However I think a missing session token in the response should be handled explicitly by ParseSwift at this code location instead of just throwing an bad response format error.

What do you think?

Keys being skipped when saving embedded Pointer

When saving a ParseObject that contains a Pointer to the server, the ParseEncoder is skipping the className and objectId keys. For example:

struct Pagina : ParseObject {
    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?
    var titolo: [String: String]?
    var parent: Pointer<Pagina>? //This will always have to be a pointer instead of an object. The same way it's represented on the server.
}

causing an error on the server:

error: This is not a valid Pointer {"code":111,"stack":"Error: This is not a valid Pointer\n    at getObjectType (/parse-server/lib/Controllers/SchemaController.js:1649:11)\n    at getType (/parse-server/lib/Controllers/SchemaController.js:1573:14)\n    at SchemaController.validateObject (/parse-server/lib/Controllers/SchemaController.js:1313:32)\n    at processTicksAndRejections (internal/process/task_queues.js:93:5)"}

ParseInstallation does not store object ID in key-value store

Assume a struct MyParseInstallation conforming to ParseInstallation.

If I understand the code correctly, calling MyParseInstallation.current?.save { (result: Result<AppInstallInfo, ParseError>) in [...] } intends to persist the ParseInstallation data to the server.

However, calling MyParseInstallation.current?.fetch(completion: { (result: Result<AppInstallInfo, ParseError>) in [...]} always returns a failure.

Problem seems to be that objectId of MyParseInstallation is not persisted to the ParseKeyValueStore. Thus, the fetch returns ParseError(code: ParseSwift.ParseError.Code.unknownError, message: "Cannot fetch an object without id")

Another effect of not storing objectId in the key-value store is that multiple calls to MyParseInstallation.current?.save result in multiple ParseInstallation objects being created on the server, all with the same data but different ids.

Tested on both the latest release and the removeDispatch branch.

ParseUser.current does not have fields set after .signup(), but it has after .login()

I have noticed that after .signup() in the client app, the ParseUser.current does not include the same data as when .login() is used:

        var newUser = PrsUser()
        newUser.email = email
        newUser.password = password
        newUser.username = email
        newUser.signup { [weak self] result in
            print("after sign-up: \(PrsUser.current)")
            ...
        }

after sign-up: Optional(_User ({"ACL":{"TYlvXr8mcV":{"write":true,"read":true}},"username":"[email protected]","objectId":"TYlvXr8mcV","createdAt":{"__type":"Date","iso":"2021-07-05T14:42:12.333Z"}}))

      PrsUser.login(username: email, password: password) { [weak self] result in
            print("after login: \(PrsUser.current)")
            ...
      }

after login: Optional(_User ({"updatedAt":{"__type":"Date","iso":"2021-07-05T14:42:14.506Z"},"maxA":30,"minA":20,"emailVerified":false,"gch":0,"ACL":{"TYlvXr8mcV":{"write":true,"read":true}},"maxD":20,"sof":true,"username":"[email protected]","objectId":"TYlvXr8mcV","email":"[email protected]","createdAt":{"__type":"Date","iso":"2021-07-05T14:42:12.333Z"}}))

calling PrsUser.current?.fetch(completion: { _ in }) right after .signup() success does not fill the missing fields. Is this intended behaviour? If so, should not PrsUser.current?.fetch(completion: { _ in }) update the missing fields?

Thank you!

Include() seems not to be working

Having two Classes, one with a Pointer to the other:

struct PointToSimple: ParseObject {
    //: These are required for any Object.
    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?

    //: Your own properties.
    var name: String?
    var age: Int = 0
    var children: Pointer<ChildClass>?
}

and

struct ChildClass: ParseObject {
    //: These are required for any Object.
    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?

    //: Your own properties.
    var name: String?
    var age: Int = 0
}

When executing a Query and including the pointer:

let pointingClassQuery = PointToSimple.query().include("children")

(also tried with include(["children"]), include("*") and includeAll())

the result brings the Pointer but not the data included in the record:

PointToSimple (
{
	"age": 1,
	"objectId": "WDgEDnFTxR",
	"updatedAt": {
		"__type": "Date",
		"iso": "2021-06-25T20:16:16.989Z"
	},
	"name": "Simple One",
	"children": {
		"__type": "Pointer",
		"className": "ChildClass",
		"objectId": "mVNojXck80"
	},
	"createdAt": {
		"__type": "Date",
		"iso": "2021-06-25T20:16:05.327Z"
	}
})

It, however, works if I use the retrieved Pointer with the fetch() method to retreive the record.

Cannot use [object Object] as a query parameter

I wrote the following code:

static func getAuthor(from book: Book, completion: @escaping ((Result<Author, Error>) -> Void)) {
    let query = Author.query().include("book").where("book" == book)
    query.first { results in
        switch results {
            case .success(let author):
                completion(.success(author))
            case .failure(let error):
                completion(.failure(error))
        }
    }
}

And expected to be able to fetch the Author that had the property "book" of type Pointer and of value of the book I passed in. (I previously fetched the Book and passed it directly into here)

Here is my Author:

struct Author: ParseObject {

    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?

    var book: Book?
}

But when the query runs I get the error ParseError(code: ParseSwift.ParseError.Code.invalidJSON, message: "You cannot use [object Object] as a query parameter.")

Note: Sorry if this by any chance possible through a different approach (and please let me know how :)) The parse sdk allowed me to do this with where(equalTo:)... so I was assuming this was a bug.

SPM Broken on v1.1.0+ and branch main

Today, I pulled the latest version of Parse-Swift and it crashes Xcode. This did not happen yesterday. It happens for versions branch main, 1.1.0 and greater. Oddly, I thought I pulled v1.1.2 earlier but could be wrong. Other SPM packages are pulled down without issue.

DO NOT TEST THIS ON A PROJECT YOU CARE ABOUT. CREATE A TEST PROJECT. THE PROJECT GOES INTO A CRASH LOOP.
Steps to reproduce:

  1. Create new iOS app.
  2. Add Parse-Swift package with versions from branch main, 1.1.0, or 1.1.2 in SPM.
  3. Xcode crashes as it pulls down the library.

@cbaker6 For some reason, I thought these versions were working earlier with SPM. Any ideas?

iOS 14 User logout does not erase session

Hi All - Wanted to check if I am having an implementation issue. I have tried calling logout both synchronously and asynchronously. No trouble removing the keychain object, but the session object in database persists.

Anybody having this issue in iOS 14?

Question about the sync saveAll call

I noticed the [].saveAll() call returns Result objects, one for each item in the array, but also throws. This is a bit confusing as now there are two routes for errors to propigate. Should we expect this call to throw if there are any errors, or if all are errors? Or some other combination? I haven't had a chance to dig through the code yet, but thought there might be a standard error handling procedure it follows.

Parse Swift can't decode custom ParseCloud errors

Throwing a custom error code from a Parse Cloud function results in:

ParseError(code: ParseSwift.ParseError.Code.unknownError, message: "Error decoding parse-server response: Optional(<NSHTTPURLResponse: 0x100d74140> { URL: http://localhost:1337/1/jobs/test } { Status Code: 200, Headers {\n    \"Content-Type\" =     (\n        \"application/json\"\n    );\n} }) with error: The data couldn’t be read because it is missing. 

Failing case:

    func testCustomError() {

        let encoded: Data = "{\"error\":\"Error: Custom Error\",\"code\":2000}".data(using: .utf8)!

        MockURLProtocol.mockRequests { _ in
            return MockURLResponse(data: encoded, statusCode: 200, delay: 0.0)
        }
        do {
            let cloud = Cloud(functionJobName: "test")
            _ = try cloud.startJob()
            XCTFail("Should have thrown ParseError")
        } catch {
            if let error = error as? ParseError {
                XCTAssertNotEqual(error.code, ParseError.Code.unknownError)
                XCTAssertEqual(error.code.rawValue, 2000)
            } else {
                XCTFail("Should have thrown ParseError")
            }
        }
    }

Proposed solution:

Add .other enum to error, and intCode property to Parse.Error

New issue with .saveAll batch operation

Sorry @cbaker6 but I'm having a new issue with .saveAll batch save/update operation since one of the last two commits.

I can't imagine it's because of the cloudcode name changes from #46 but maybe there is a cloud code that runs in parse-server source that I am not aware of. I scanned through #43 and I'm going to take a flyer and say it might be the skippedKeys or other changes to the batch command in API+Commands.swift. I don't see the error but I don't know enough about the return headers and body to know how it is encoded.

Also, I think Parse has always done this, but I get a success response with an array of parseerrors. So, it's a success but the saveAll failed.

Here's the code:

/// I have something like this where GameScore.scores is a [String]?
let score = GameScore(score: ["2"])
let score2 = GameScore(score: ["3"])
let scoresArray = [score, score2]

scoresArray.saveAll { results in
    switch results {
    case .success(let success):
        var index = 0
        success.forEach { success in
            switch success {
            case .success(let savedScore):
                print("Saved \"\(savedScore.className)\" with score \(savedScore.score) successfully")
                if index == 1 {
                    print("\(savedScore)")
                }
                index += 1
            case .failure(let error):
                print("Error saving: \(error)") // <---All the objects fail now after the recent commits and scores are not updated remotely.
            }
        }

    case .failure(let error):
        print("Error batch saving: \(error)")
    }
}
// This is the response from the `error` in the first try-catch
[Swift.Result<ParseApp.GameScore, ParseSwift.ParseError>.failure(ParseSwift.ParseError(code: ParseSwift.ParseError.Code.invalidKeyName, message: "Invalid field name: __type.")), Swift.Result<ParseApp.GameScore, ParseSwift.ParseError>.failure(ParseSwift.ParseError(code: ParseSwift.ParseError.Code.invalidKeyName, message: "Invalid field name: __type."))]

I thought there may be some issue with a pointer, missing column, or my key strings in query, because Invalid field name:__type. in the response, but after some inspection it seems to be a potential issue with encoding the query. Everything is in order and looking at history, it seemed to work before. I could be wrong of course. I may try rolling back and testing, but for now, it would be nice to have another pair of eyes who knows Parse-Swift well.

There are no issues updating remote objects, if I do a for-loop and do individual .save.

Decoding error when saving nested object

Hi all,

I am encountering an issue when saving a nested object.

I have a teacher object that has a name (string) and a language (custom object)

struct teacher : ParseObject {
    
    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?
    
    var name : String
    var langauge : language
}
struct language : ParseObject{
    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?
    
    var code : String
}

I have no problem creating and saving language objects, but when I try to save a Teacher object I am getting a decoding error:

ParseError(code: ParseSwift.ParseError.Code.unknownError, message: "decoding error: typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))")

I am running the parse server on a localhost. macOS Big Sur and SwiftUI.

I have also tried saving the example of Author and Book in the Swift Playgrounds example code but I am getting exactly the same output.

Any one would know how can I solve this? Or what I am missing?

Kind regards,
Pau

AnyCodable no longer public

In a recent version of the SDK, AnyCodable (and the En/Decodable versions) were changed from public to internal. Our project used AnyDecodable as part of a Cloud code return value:

struct CheckUserStatusCloudFunction: ParseCloud {
	typealias ReturnType = [String: AnyDecodable]
	
	var functionJobName = "checkUserStatus"
	
	var platform = "ios"
	var appIdentifier: String
}

The server returns a dictionary where the keys are all strings, and the values are various codable values like string, bool, int, etc. Normally we would create a struct to contain the results, but this specific functionality needs to be generic and could be used by different apps and the result may have different keys so it can't be statically typed in that way.

I can just add my own version of AnyCodable to the project, but this seems like a lot of duplication for something that already exists in the project. Can this be made public again?

ParseKeyValueStore in iOS app

Hello,

I am struggling to understand what is the idea of keyValueStore in the ParseSwift initializer. These are my first steps with Parse and this SDK also as I decided to not use Firebase at the end.

Starting with simple init in AppDelegate:

ParseSwift.initialize(applicationId: "...", clientKey: "...", serverURL: URL(string: "https://...")!)

For the learning purpose I created user struct:

`struct ParseUsr: ParseUser {

//: These are required for `ParseObject`.
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?

//: These are required for `ParseUser`.
var username: String?
var email: String?
var password: String?
var authData: [String: [String: String]?]?

}`

then when my app launch I am checking if current user is nil, for simplicity, just print it:

print(ParseUsr.current)

but upon retrieving there is a nil on line 46, ParseStorage.swift -> "Unexpectedly found nil while implicitly unwrapping an Optional value" for backingStore

later I tried also to add keyValueStore in the initialiser:

ParseSwift.initialize(applicationId: "...", clientKey: "...", serverURL: URL(string: "https://...")!, keyValueStore: KeyChain())

class for KeyChain is empty implementation so far, but that should not cause the above nil exception on backingStore:

`class KeyChain: ParseKeyValueStore {

func delete(valueFor key: String) throws {
     
}

func deleteAll() throws {
     
}

func get<T>(valueFor key: String) throws -> T? where T : Decodable {
    return nil
}

func set<T>(_ object: T, for key: String) throws where T : Encodable {
     
}

}`

Could you please give me a hint how to use the local persistence of current user?

Thank you!

Fetch objects that contains pointers requires include for each or includeAll

Since Swift is strongly typed, and the decoder is expecting objects that conform to ParseObject instead of a Pointer, objects that are pointed to need to be included or a decoding error will occur when fetching data from the server.

Not sure of a fix, but need to see how the obj-c framework handles this

URLSessionWebSocketTask unavailable on Linux

Building this project on Linux is currently broken.

Problem is, URLSessionWebSocketTask is not available on Linux, not even in FoundationNetworking.

For reference, this is for example discussed in the swift forum: "Is there a WebSocketTask in FoundationNetworking?"

Solution proposed there was using websocket-kit.

For now, to make my code compile locally, I added #if !os(Linux) in ParseLiveQueryDelegate.swift.

Also, it might be a good idea to add a Linux build to continuous integration to catch Linux issues more early.

Session expiresAt stored?

@cbaker6 Does anybody know if the session expiresAt date is stored on login or if I need to make an additional query to
Session directly or apply become(session)?

Crash after calling subscribeCallback

Hi,

In my project, I am currently changing Parse-SDK-iOS-OSX & ParseLiveQuery-iOS-OSX to Parse-Swift (1.8.6.) client.
Everything went very well, except for parse live query. Live query is working with ParseLiveQuery-iOS-OSX so I assume that parse server is configured properly and does not require any other configuration.

This is my parseSwift setup:

ParseSwift.initialize(applicationId: Constants.Parse.applicationId,
                      clientKey: Constants.Parse.clientKey,
                      serverURL: serverURL)

This is how i am trying to get subscribeCallback:

var subscription: SubscriptionCallback<PUser>?
        
func testLiveQuery() {
      guard let userId = PUser.current?.objectId else { return }

      let query = PUser.query("objectId" == userId)
      subscription = query.subscribeCallback
}

subscription is a global variable. Class that stores it, is not deallocated.

and after calling query.subscribeCallback I am getting EXC_BAD_ACCESS. On concurrent thread I found this:

Screenshot 2021-07-14 at 16 14 56

I also tested subscribeCallback with parse live query client with the same result.

When I want to create new parse live query client or use the one that exists I get the same error. So basically the error occurs when I want to do something with the parse live query client and not only when subscribing to the query.

Am I doing something wrong here?

ParseInstallation.current is always nil after ParseUser.logout

When ParseUser.logout is called it also clears the current ParseInstallation by calling ParseInstallation.deleteCurrentContainerFromKeychain. This is expected.

static func deleteCurrentKeychain() {
deleteCurrentContainerFromKeychain()
BaseParseInstallation.deleteCurrentContainerFromKeychain()
BaseConfig.deleteCurrentContainerFromKeychain()
}

internal static func deleteCurrentContainerFromKeychain() {
try? ParseStorage.shared.delete(valueFor: ParseStorage.Keys.currentInstallation)
#if !os(Linux) && !os(Android)
try? KeychainStore.shared.delete(valueFor: ParseStorage.Keys.currentInstallation)
#endif
}

Afterwards ParseInstallation.current always returns nil.

Expected: A new ParseInstallation is created when ParseInstallation.currentInstallationContainer.currentInstallation is nil.

I think this could be the right place to create a new installation when its missing:

var newInstallation = CurrentInstallationContainer<Self>()
let newInstallationId = UUID().uuidString.lowercased()
newInstallation.installationId = newInstallationId
newInstallation.currentInstallation?.createInstallationId(newId: newInstallationId)
newInstallation.currentInstallation?.updateAutomaticInfo()
try? KeychainStore.shared.set(newInstallation, for: ParseStorage.Keys.currentInstallation)
try? ParseStorage.shared.set(newInstallation, for: ParseStorage.Keys.currentInstallation)

I currently see no way to create a new ParseInstallation object from outside the framework. The only place in code I found which actively sets a new installation is ParseInstallation.updateKeychainIfNeeded. This only gets called on fetch, save or similar requests. However I can not call fetch or save when ParseInstallation.current is nil.

Sign In with Apple + custom parameters

Hello,

I already had implemented Apple Sign-In and had this working. Down the way I wanted to extend the User class with some custom fields and some I set as required in the Parse Dashboard. But the following code is now not working even the required fields are defined at struct init:

func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
            guard let token = appleIDCredential.identityToken else {
                fatalError("identity token is nil")
            }
            var newUser = PrsUser()
            newUser.username = appleIDCredential.email
            newUser.email = appleIDCredential.email
            var acl = ParseACL()
            acl.publicRead = false
            acl.publicWrite = false
            acl.setReadAccess(user: newUser, value: true)
            acl.setWriteAccess(user: newUser, value: true)
            newUser.ACL = acl
            print("newUser: \(newUser)")
            newUser.apple.login(user: appleIDCredential.user, identityToken: token) { result in
                switch result {
                case .success(let user):
                    print("login response: \(user)")
                    if self.userController?.currentProfile.gender == Gender.notDefined.rawValue {
                        self.showGenderSelection(animated: false, newPrsUser: user)
                    } else {
                        self.showSyncProgress(animated: false, syncUser: user)
                    }
                case .failure(let error):
                    let errorText = "Error \(error.code.rawValue) - \(error.message)"
                    UIAlerts.alertWith(message: errorText, viewController: self)
                    print("Error logging in: \(error.localizedDescription)")
                }
            }
        }
    }

the print right before apple.login() shows following:

newUser: _User ({"maxA":30,"minA":20,"maxD":20,"ACL":{}})

but the login results in error:

Error logging in: ParseError code=142 error=minA is required

When I set the fields as not required in the parse dashboard, the login proceed without error. It is not a big deal for me now, as I can save the fields in the second call. But it confused me quite a lot because I use the same logic also for sign-up with email and there it is throwing now error even when the same fields are initialised the same:

func signup() {
        guard let email = emailTextField.text, let password = passwordTextField.text else { return }
        var newUser = PrsUser()
        newUser.email = email
        newUser.password = password
        newUser.username = email
        var acl = ParseACL()
        acl.publicRead = false
        acl.publicWrite = false
        acl.setReadAccess(user: newUser, value: true)
        acl.setWriteAccess(user: newUser, value: true)
        newUser.ACL = acl
        print("newUser: \(newUser)")
        newUser.signup { result in
            switch result {
            case .success(let user):
                ...
            case .failure(let error):
                ...
            }
        }
    }

Did I understood the logic behind apple login wrong or might this indeed be a bug?

Thank you!

App crash when using User.current

An app crash can occur depending on how User.current is used. The error received is:

Thread 1: Simultaneous accesses to 0x107700288, but modification requires exclusive access

To replicate, I call ParseACL.defaultACL in the appDelegate (didFinishLaunchingWithOptions) and then call User.current in a RootView ViewController (viewDidLoad).

The specific console log output from Xcode is below:

Simultaneous accesses to 0x107700288, but modification requires exclusive access.
Previous access (a modification) started at OCKSample`static ParseUser.currentUserContainer.getter + 351 (0x1075cab1f).
Current access (a modification) started at:
0    libswiftCore.dylib                 0x00007fff2f41f890 swift_beginAccess + 568
1    OCKSample                          0x00000001075ca9c0 static ParseUser.currentUserContainer.getter + 351
2    OCKSample                          0x00000001075cc460 static ParseUser.current.getter + 161
3    OCKSample                          0x00000001075f6590 static ParseACL.defaultACL() + 349
4    OCKSample                          0x0000000106fe4dc0 User.init(from:) + 598
5    OCKSample                          0x0000000106fe8020 protocol witness for Decodable.init(from:) in conformance User + 15
6    libswiftCore.dylib                 0x00007fff2f3eb910 dispatch thunk of Decodable.init(from:) + 7
7    libswiftFoundation.dylib           0x00007fff53afe620 __JSONDecoder.unbox_(_:as:) + 3337
8    libswiftFoundation.dylib           0x00007fff53af37f0 _JSONKeyedDecodingContainer.decode<A>(_:forKey:) + 637
9    libswiftFoundation.dylib           0x00007fff53af6220 protocol witness for KeyedDecodingContainerProtocol.decode<A>(_:forKey:) in conformance _JSONKeyedDecodingContainer<A> + 45
10   libswiftFoundation.dylib           0x00007fff53af5fa0 protocol witness for KeyedDecodingContainerProtocol.decode<A>(_:forKey:) in conformance _JSONKeyedDecodingContainer<A> + 24
11   libswiftCore.dylib                 0x00007fff2f140b10 KeyedDecodingContainerProtocol.decodeIfPresent<A>(_:forKey:) + 365
12   libswiftFoundation.dylib           0x00007fff53af61e0 protocol witness for KeyedDecodingContainerProtocol.decodeIfPresent<A>(_:forKey:) in conformance _JSONKeyedDecodingContainer<A> + 24
13   libswiftCore.dylib                 0x00007fff2f14dc80 _KeyedDecodingContainerBox.decodeIfPresent<A, B>(_:forKey:) + 266
14   libswiftCore.dylib                 0x00007fff2f1413d0 KeyedDecodingContainer.decodeIfPresent<A>(_:forKey:) + 39
15   OCKSample                          0x00000001075c9e70 CurrentUserContainer.init(from:) + 725
16   OCKSample                          0x00000001075ca930 protocol witness for Decodable.init(from:) in conformance CurrentUserContainer<A> + 26
17   libswiftCore.dylib                 0x00007fff2f3eb910 dispatch thunk of Decodable.init(from:) + 7
18   libswiftFoundation.dylib           0x00007fff53afe620 __JSONDecoder.unbox_(_:as:) + 3337
19   libswiftFoundation.dylib           0x00007fff53ae75e0 JSONDecoder.decode<A>(_:from:) + 759
20   libswiftFoundation.dylib           0x00007fff53b01bd0 dispatch thunk of JSONDecoder.decode<A>(_:from:) + 15
21   OCKSample                          0x00000001076268e0 CodableInMemoryPrimitiveObjectStore.get<A>(valueFor:) + 464
22   OCKSample                          0x0000000107627030 protocol witness for PrimitiveObjectStore.get<A>(valueFor:) in conformance CodableInMemoryPrimitiveObjectStore + 13
23   OCKSample                          0x0000000107625b70 ParseStorage.get<A>(valueFor:) + 354
24   OCKSample                          0x00000001075ca9c0 static ParseUser.currentUserContainer.getter + 447
25   OCKSample                          0x00000001075cc460 static ParseUser.current.getter + 161
26   OCKSample                          0x0000000106fc44d0 CareViewController.synchronizeWithRemote() + 156
27   OCKSample                          0x0000000106fc56b0 @objc CareViewController.synchronizeWithRemote() + 43
28   CoreFoundation                     0x00007fff2037bc3a __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12
29   CoreFoundation                     0x00007fff2037bbe4 ___CFXRegistrationPost_block_invoke + 49
30   CoreFoundation                     0x00007fff2037b16d _CFXRegistrationPost + 454
31   CoreFoundation                     0x00007fff2037a985 _CFXNotificationPost + 796
32   OCKSample                          0x0000000106fd95e0 closure #1 in CloudSyncSessionDelegate.session(_:activationDidCompleteWith:error:) + 367
33   OCKSample                          0x0000000106fc55a0 thunk for @escaping @callee_guaranteed () -> () + 48
34   libdispatch.dylib                  0x00000001082788a0 _dispatch_call_block_and_release + 12
35   libdispatch.dylib                  0x0000000108279a80 _dispatch_client_callout + 8
36   libdispatch.dylib                  0x0000000108287aa3 _dispatch_main_queue_callback_4CF + 1152
37   CoreFoundation                     0x00007fff203a826d __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
38   CoreFoundation                     0x00007fff203a2089 __CFRunLoopRun + 2685
39   CoreFoundation                     0x00007fff203a1967 CFRunLoopRunSpecific + 567
40   GraphicsServices                   0x00007fff2b793d28 GSEventRunModal + 139
41   UIKitCore                          0x00007fff2466d07f -[UIApplication _run] + 912
42   UIKitCore                          0x00007fff246722bb UIApplicationMain + 101
43   OCKSample                          0x0000000106fdb700 main + 75
44   libdyld.dylib                      0x00007fff20257408 start + 1

Invalid Session Token after Signup

I see invalid session token errors after signup. This is my signup code:

    public func signupWith(username: String, email: String, password: String) {
        guard PodliveUser.current != nil else {
            fatalError("Can not signup because current user is nil")
        }
        PodliveUser.current?.username = username
        PodliveUser.current?.password = password
        PodliveUser.current?.email = email
        PodliveUser.signup(username: username, password: password) { [self] (result) in
            switch result {
            case .success(var user):
                user.save { (resut) in
                    switch result {
                    case .success(_):
                        NotificationCenter.default.post(name: Notification.Name.userUpdated, object: nil)
                    case .failure(let error):
                        print("Could not save email after signup: " + error.localizedDescription)
                    }
                    NotificationCenter.default.post(name: Notification.Name.userUpdated, object: nil)
                }
            case .failure(let error):
                print("Could not signup " + error.localizedDescription)
            }
        }
    }

I do not see invalid session token errors after login only after signup.

I use ParseSwift 1.7.2 with Xcode 12.5.

Detecting disconnected LiveQuery

I am trying to build a robust way how to detect any missed liveQuery updates due to connectivity issue with the playground code example combined with NWPathMonitor():

    var subscription: SubscriptionCallback<PrsProfile>?
    
    let monitor = NWPathMonitor()
    
    func testLiveQueryOnProfile() {
        print("testing live query for objectId: \(String(describing: PrsUser.current?.objectId))")
        //testing LiveQuery only
        if let objectId = PrsUser.current?.objectId {
            //: Create a query just as you normally would.
            let query = PrsProfile.query("objectId" == objectId)
            //: This is how you subscribe to your created query using callbacks.
            subscription = query.subscribeCallback!
            print("cached subscription: \(String(describing: subscription))")
            //: This is how you receive notifications about the success
            //: of your subscription.
            subscription!.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 notificaitons of events related to your LiveQuery.
            subscription!.handleEvent { _, event in
                switch event {

                case .entered(let object):
                    print("LQ Entered: \(object)")
                case .left(let object):
                    print("LQ Left: \(object)")
                case .created(let object):
                    print("LQ Created: \(object)")
                case .updated(let object):
                    print("LQ Updated: \(object)")
                case .deleted(let object):
                    print("LQ Deleted: \(object)")
                }
            }
            
            //: This is how you register to receive notificaitons about being unsubscribed.
            subscription!.handleUnsubscribe { query in
                print("Unsubscribed from \(query)")
            }
            
            monitor.pathUpdateHandler = { path in
                if path.status == .satisfied {
                    // Indicate network status, e.g., back to online
                    print("back to online")
                } else {
                    // Indicate network status, e.g., offline mode
                    print("offline mode")
                }
            }
            monitor.start(queue: .global())
            
        }
    }

for testing I am using Xcode's Network Link Conditioner. I am not able to detect when the LiveQuery socket or subscription got lost, as the NWPathMonitor() is not reliable in this case (connection path did not change, only 100% loss occurs) and the SubscriptionCallback does not call any event.

What is the correct way to get notified when LiveQuery is not anymore listening? When I test this procedure: block connection bandwidth, app to background for a moment, app resumes active, unblock the traffic.. the following return true even when there are no events being received anymore:

ParseLiveQuery.getDefault()?.isConnected

The client should know when it should re-fetch actual state and subscribe again to the queries. I struggle to find out how to detect that moment

handling error in .saveAll()

I am learning how to efficiently save objects in batch and how to properly handle the errors and I noticed that the code is throwing only one error it gets from the batch. Here is a simple example and I try to upload 2 objects that are not yet on the server - one with objectId = nil and other with defined objectId:

    objects.saveAll { result in
            switch result {
            case .success(let saveResults):
                for result in saveResults {
                    switch result {
                    case .success(let savedObject):
                        savedObjects.append(savedObject)
                    case .failure(let error):
                        assertionFailure("error saving oblect: \(error.localizedDescription)")
                    }
                }
            case .failure(let error):
                self.handle(error: error, syncCategory: .inboxObjects, localId: "all")
            }
        } 

What is a bit confusing for me is that the .success(let saveResults) is also a Result but the failure in that result seems to not be reached as the .saveAll() catch the failure according to lines:

callbackQueue.async {
if let parseError = error as? ParseError {
completion(.failure(parseError))
} else {
completion(.failure(.init(code: .unknownError, message: error.localizedDescription)))
}
}

So in this example, because I use allowCustomObjecId the first object throws:

ParseError code: missingObjectId

But the second object does not throw anything as the function does not come to the second object. What makes sense, as it tries to construct command and gets stopped by following guard (although I am getting lost in the code so I might be wrong):

if ParseSwift.configuration.allowCustomObjectId && objectable.objectId == nil {
throw ParseError(code: .missingObjectId, message: "objectId must not be nil")
}

My question would be then... If the .saveAll() fails due to one object in array, how can I find out what object it was so that I could exclude it from repeated upload or show warning in the UI?

ParseUser.delete() does not remove session from server

Hi,

I am not sure if it is but or desired behaviour... After calling PrsUser.current?.delete(completion: { result in ... }) in the client app the user object gets removed from server, but the session object remains intact.

Calling PrsUser.logout { result in ... } does remove the session, so should I log out user first before deleting the account, or could we achieve it in one call?

What I want to achieve is to remove account together with related sessions, if user decide to stop using my app.

Thank you!

.select() in finds and firsts does not seem to exclude any fields

Hey @cbaker6 - Do you think this could be an encoding problem or a potential parse-server bug?

struct GameScore: ParseObject {
    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?

    var score: Int?
}

var score = GameScore()
score.score = 200
try score.save()

let afterDate = Date().addingTimeInterval(-300)
var query = GameScore.query("createdAt" > afterDate).select(["score"]).limit(1)

query.find() { results in
    print(results) /// <<< I would expect only the GameScore objects with createdAt after afterDate to only return the "score" field. (Yes, this is lazy.)
}

/// Alternatively
query.first() { results in
    print(results)
}

Anybody have any thoughts? Will dig deeper into the source, if not. Thanks!

EDIT:
.select() does seem to work, as of late. Reference: https://stackoverflow.com/questions/61100282/parse-server-select-a-few-fields-from-included-object

Update ParseFile with already known url

I am able to save the ParseFile struct encoded as data in to a local realm database and then convert it back, but after I try to update the content i cannot get it saved:

file.save { results in
      switch results {
      case .success(let savedFile):
          ....
      case .failure(let error):
          print("error saving file: \(file)")
          print(error)
      }

error saving file: ParseFile(__type: "File", localId: 2478C4A2-3302-46D1-BB75-19A0E7B09641, name: "2adcf21bf5a9bb8223ba693fd58963dc_0.jpg", url: Optional(https://parsefiles.back4app.com/coYfuTYV3z35Z8iwv8q3NOTdGQs2ywJtt7tUUg4p/2adcf21bf5a9bb8223ba693fd58963dc_0.jpg), localURL: nil, cloudURL: nil, data: Optional(71997 bytes), mimeType: nil, metadata: nil, tags: nil, options: Set([]))

ParseError(code: ParseSwift.ParseError.Code.unknownError, message: "Error decoding parse-server response: Optional(<NSHTTPURLResponse: 0x7feec2f04d00> { URL: https://parseapi.back4app.com/files/2adcf21bf5a9bb8223ba693fd58963dc_0.jpg } { Status Code: 404, Headers {... }) with error: The data couldn’t be read because it is missing. Format: Optional("{\"message\":\"Not Found\",\"error\":{}}")")

as you can see the url in request is not the url that I have already in the file. Is it correct that the the following SDK code is generating the url again?

private static func updateFileCommand(_ object: ParseFile) -> API.Command<ParseFile, ParseFile> {
        API.Command(method: .PUT,
                    path: .file(fileName: object.name),
                    uploadData: object.data,
                    uploadFile: object.localURL) { (data) -> ParseFile in
            try ParseCoding.jsonDecoder().decode(FileUploadResponse.self, from: data).apply(to: object)
        }
    }

the path: .file(fileName: object.name), seems to get it wrong.. shouldn't there be path: object.url ?

Query for Null property

how to query for nil value?

let query = Pagina.query("stato" == "PUBBLICATA", "parent" == nil)

i have

Generic parameter 'T' could not be inferred

Remove automatic retrieval of badge in ParseInstallation

Getting the badge value automatically is something native to the Objective-c SDK (and currently Parse-Swift). It requires using UIKit or AppKit to retrieve and accessing it properly means ParseInstallation has to always be called on the main queue. This leads to awkward implementation and usage of ParseInstallation. In addition, Parse-Swift works on OSs that don't have badges like Linux and Android.

I plan to remove the automatic retrieval of badge. If a developer wants to populate this field they can create their own dispatches before adding it to ParseInstallation.

This will lead to easier usage and less buggy and more predictable (example parse-community/Parse-SDK-iOS-OSX#1568) behavior when using ParseInstallation.

Feel free to comment with your thoughts...

User.current not returning all variables

When a user is logged in, and you request a custom key from the user, it returns nil even though there is a value for that user in the database.

This is my ParseUser structure

struct User: ParseUser {
            //: These are required for ParseObject
            var objectId: String?
            var createdAt: Date?
            var updatedAt: Date?
            var ACL: ParseACL?

            //: These are required for ParseUser
            var username: String?
            var email: String?
            var emailVerified: Bool?
         
            //: Custom
            var userId: String?
 }

This is what I call on launch

let currentUser = User.current
        print("currentUser - objectId: \(String(describing: currentUser?.objectId))")
        print("currentUser - createdAt: \(String(describing: currentUser?.createdAt))")
        print("currentUser - updatedAt: \(String(describing: currentUser?.updatedAt))")
        print("currentUser - ACL: \(String(describing: currentUser?.ACL))")
        
        print("currentUser - username: \(String(describing: currentUser?.username))")
        print("currentUser - email: \(String(describing: currentUser?.email))")
        print("currentUser - emailVerified: \(String(describing: currentUser?.emailVerified))")
        //Custom key
        print("currentUser - userId: \(String(describing: currentUser?.userId))")

This is the log
Schermafbeelding 2020-12-20 om 00 40 06

This is an example of 3 users in Parse database
Schermafbeelding 2020-12-20 om 00 46 07

Parse Installation

I have an issue where the installation always prints an error.
This is my code:

@main
struct MyApp: App {
    
    init() {
        ParseSwift.initialize(applicationId: "id", clientKey: "key", serverURL: URL(string: "https://myapp.back4app.io")!)
        DispatchQueue.main.async {
            Installation.current?.save { results in
                switch results {
                case .success(_):
                    print("[Novem] Succesufully saved Installation to ParseServer")
                case .failure(let error):
                    print("[Novem] Failed to update installation: \(error)")
                }
            }
        }
    }
    var body: some Scene {
        WindowGroup {
            MainView()
        }
    }
}

I get this error every time my app launches

Failed to update installation: ParseError(code: ParseSwift.ParseError.Code.unknownError, message: "Error decoding parse-server response: Optional(<NSHTTPURLResponse: 0x281592960> { URL: https://myapp.back4app.io/installations } { Status Code: 200, Headers {\n \"Access-Control-Allow-Origin\" = (\n \"*\"\n );\n \"Content-Encoding\" = (\n gzip\n );\n \"Content-Type\" = (\n \"application/json; charset=utf-8\"\n );\n Date = (\n \"Sun, 14 Mar 2021 14:13:47 GMT\"\n );\n Etag = (\n \"W/\\\"28-P0BYdfTXNbkssuaZhMIRXlMHiBo\\\"\"\n );\n Server = (\n nginx\n );\n \"Transfer-Encoding\" = (\n Identity\n );\n \"access-control-allow-credentials\" = (\n true\n );\n \"access-control-allow-headers\" = (\n \"DNT, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, X-Application-ID, X-Access-Token, X-Parse-Master-Key, X-Parse-REST-API-Key, X-Parse-Javascript-Key, X-Parse-Application-Id, X-Parse-Client-Version, X-Parse-Session-Token, X-Requested-With, X-Parse-Revocable-Session, X-CSRF-Token, X-Parse-Client-Key\"\n );\n \"access-control-allow-methods\" = (\n \"GET, POST, PUT, PATCH, DELETE, OPTIONS\"\n );\n \"access-control-expose-headers\" = (\n \"X-Parse-Job-Status-Id, X-Parse-Push-Status-Id\"\n );\n \"x-powered-by\" = (\n Express\n );\n} }) with error: The data couldn’t be read because it is missing.")

struct Installation: ParseInstallation {
    //: These are required for ParseObject
    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?

    //: These are required for ParseInstallation
    var installationId: String?
    var deviceType: String?
    var deviceToken: String?
    var badge: Int?
    var timeZone: String?
    var channels: [String]?
    var appName: String?
    var appIdentifier: String?
    var appVersion: String?
    var parseVersion: String?
    var localeIdentifier: String?
}

Custom objectId not working when there is a cloud function trigger "beforeSave"

Hello, I am not sure if this is related to ParseSwift of rather the Sever itself, but when I use following function in Cloud Code, my custom objectId is ignored upon saving a new document to database:

Parse.Cloud.beforeSave("PrsProfile", async (request) => {
    if (request.original == null) {
        //indexing to ElasticSearch before save to define ElasticSearch document id
        const esResult = await elastic.indexProfile(request.object);
        if (esResult != null) {
            request.object.set("es", esResult);
            const randomInt = Math.floor(Math.random() * (262144 - 1)) + 1; //bloom filter index in range 1...262144 (256*1024)
            request.object.set("bf", randomInt);
            request.log.info(`indexed new profile before saving to server database: ${esResult}}`);
        } else {
            throw "ElasticSearch ID was not defined, document was not saved and indexed";
        }
    }
});

As the function does not manipulate objectId neither the referred index function, I am a bit confused. Commenting out this trigger makes the custom objectId work well again. Also trigger "afterSave" does not break anything.

Just for completeness, here is the referred index function:

exports.indexProfile = async function(profile) { //PrsProfile
    const currentLoc = new Parse.GeoPoint(profile.get("gp"));
    const speakLangs = [];
    const native = profile.get("nl");
    const speak = profile.get("sl");
    const learnLangs = profile.get("ll");
    const trips = profile.get("gt");
    if (native != null & Array.isArray(native)) {
        speakLangs.push(...native);
    }
    if (speak != null & Array.isArray(speak)) {
        speakLangs.push(...speak);
    }
    const { body } = await esClient.index({        
        id: profile.get("es"), //if null, then new index is created
        index: "profile",
        body: {
            //TODO: check how updates are handeled
            ag: profile.get("ag"),
            gn: profile.get("gn"),
            d: profile.get("d"),
            o: profile.get("o"),
            po: profile.get("po"),
            sl: (speakLangs.length > 0 ? speakLangs : []), 
            ll: ((learnLangs != null & Array.isArray(learnLangs)) ? learnLangs : []),
            gp: [currentLoc.latitude, currentLoc.longitude], 
            on: profile.get("on"), 
            da: profile.get("da"), 
            tn: profile.get("tn"), 
            tr: profile.get("tr"), 
            co: profile.get("co"), 
            gt: ((trips != null & Array.isArray(trips)) ? trips : []), 
            fd: profile.get("fd"),
            bf: profile.get("bf") //bloom filter int value
        }
    })
    //pass back an ES document id to be saved in MongoDB, if creating new
    return body._id;
}

Date not uploading correctly for different timezones

When I update a custom date value to Parse, it does something wrong with the timezone. If I look in the database, it clearly shows that it added +1 hour. But when I read it in the app with ParseSwift, it shows it correctly.

I think there is something wrong with the conversion of Dates when uploading and reading from Parse.

We also have a website, and with the Parse API for PHP it does upload the correct date. So if we look in the database, the timeStamp is the same as updatedAt. But when reading that same timeStamp value in ParseSwift, it makes the date -1 hour. This is causing some issues for us.

I have this code:

let query = AppFiles.query("score" == score)
query.first(callbackQueue: .main) { results in
    switch results {
    case .success(var userFile):
        userFile.timeStamp = Date()
        
        userFile.save { result in
            switch result {
                case .success(_):
                    print("SUCCES")
                case .failure(let error):
                    print("FAIL")
                    print(error)
            }
        }
    case .failure(let error):
        print(error)
    }
}

This is a picture of what the dates look like when it is updated from the ParseSwift API:
Schermafbeelding 2021-03-27 om 16 37 10

So to wrap this issue up:

When updating the timeStamp from ParseSwift:

Dashboard shows this: 2021-03-27 16:31:40 +0000
Logging in swift shows this: 2021-03-27 15:31:40 +0000

When updating the timeStamp from Parse PHP:

Dashboard shows this: 2021-03-27 15:31:40 +0000
Logging in swift shows this: 2021-03-27 14:31:40 +0000

Missing error when saving a user with an email which already exists on the server

Parse Server enforces unique email addresses on the users collection.

Currently the signup function only takes a username and password.

Setting the email of the user and saving after signup does succeed when the email does not already exists. However if the email already exists no error occurs on the client side.

This is my signup code:

    public func signupWith(username: String, email: String, password: String) {
        guard PodliveUser.current != nil else {
            fatalError("Can not signup because current user is nil")
        }
        PodliveUser.current?.username = username
        PodliveUser.current?.password = password
        PodliveUser.current?.email = email
        PodliveUser.signup(username: username, password: password) { [self] (result) in
            switch result {
            case .success(var user):
                // save user again to store email on the server
                user.save { (resut) in
                    switch result {
                    case .success(_):
                        NotificationCenter.default.post(name: Notification.Name.userUpdated, object: nil)
                    case .failure(let error):
                        print("Could not save email after signup: " + error.localizedDescription)
                    }
                    NotificationCenter.default.post(name: Notification.Name.userUpdated, object: nil)
                }
            case .failure(let error):
                print("Could not signup " + error.localizedDescription)
            }
        }
    }

I expected to see the error Could not save email after signup. However the email of the user is set locally but is not persisted on the server (if a user with the email already exists).

An even better approach in my eyes is to include the email in the signup function directly so I do not have to do multiple network calls.

I use ParseSwift 1.7.2 with Xcode 12.5.

New Installations created every time

In the Android SDK, the device stores its installation. When .current is called again later, the same installation is saved.

I am saving Installation.current?, however each time the app is reopened a new Installation object is saved in the database.

Does anyone have any ideas?

initialize in SwiftUI App Protocol

Is it wise to initialize Parse on the mainview .onappear?
Since there is no applicationDidFinishLaunching anymore, what is the best way to do it?

I know there is a workaround to still be able to use applicationDidFinishLaunching, but i think its not prefered.

Tree Parse object

Hi i have a Tree object

with parent is object of the same type

how can i solve this?

struct Pagina : ParseObject {
    var objectId: String?
    var createdAt: Date?
    var updatedAt: Date?
    var ACL: ParseACL?
    var titolo: [String: String]?
    var parent: Pagina? <--- i can't do this because we are in a struct...
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.