Configure `Installation`-Schema / access custom fields

I am using the Installation-Model to allow users to configure which (push) Notifications they want to receive on which device, without having to sign up. Unfortunately, it seems the Installation-Object acts differently than other models. To allow for a smooth setup, I set the channel based on a custom field on first iteration, but then want to allow the user to change them. The parse-code looks like this:


Parse.Cloud.beforeSave(Parse.Installation, async (request) => {
  const defaultTeamId = request.object.get("defaultTeamId");
  request.object.unset("defaultTeamId");

  if (!request.master) {
    if (request.user) {
      request.object.set("user", request.user);
    } else {
      request.object.unset("user");
    }
  }

  if (request.original) {
    // an update not a create, ignore startingFollows
    // but let it return the existing set of channels:
    console.log("known");
    request.object.set('channels', request.original.get('channels'));
    return
  }

  const channels = []

  if (request.user) {
    const { teams } = await fetchMyTeams(request.user);
    teams.forEach((x) => {
      channels.push(x.id)
    });
  }

  if (channels.length === 0 && defaultTeamId) {
    channels.push(defaultTeamId);
  }

  request.object.set("channels", channels.map((x) => `${x}:news`));
});

This generally works as expected, just that the channels are not returned to the user, nor the id of the object when it set/created:

{"updatedAt":"2021-02-21T16:03:05.733Z","defaultTeamId":{"__op":"Delete"},"user":{"__op":"Delete"}}

I noticed that there is an empty [] in the protectedFields of the schema:

"protectedFields": {
            "*": []
        }

Which I can’t change either. Is that related? How can I get the parse-server to return the other fields? Especially since the client doesn’t even get the objectId?

Thanks!

How are you saving the Installation?

Where do you see this object?
{"updatedAt":"2021-02-21T16:03:05.733Z","defaultTeamId":{"__op":"Delete"},"user":{"__op":"Delete"}}

using Ionic/Capacitor, once I have the necessary information, it is generated here, which is triggered at app-start up, and just issues installation.save():

This comes back on the wire/via the network as the response for save - as seen in the browser inspector. I was hoping to see the change of channels, too, but it doesn’t show :frowning: .

The API only return the fields that were changed so, as peer as my understanding of your code, you should see the channels field being returned when creating a new installation (but not when updating). Do you see a different behavior?

How is that being determined though? Meaning, I don’t have the objectID for the instance for example, and unlike with other models, for Installation none is returned ever, even when creating. If I had that I’d just use it to fetch the entire objects, but afaiu, installation.fetch() doesn’t work for the same reason (doesn’t have an id).

I guess the idea is we return only what has been asked for and been changed on the model during the processing, in the assumption that the other information is known to the sender - but in this case they aren’t.

No, that is what I see (well aside from the oddities around id). But I am wondering whether or how I could make it return other values. Or is it better to just write my own cloud-code function then?

Cheers

The information (including the id) should be returned when creating a new installation: REST API Guide | Parse

Would you mind to share the code that you are using to create the installation and what you see being returned?

I am not using the Rest-API but the Javascript API (Parse.Installation()) directly. The code is spread a bit all over the place, but essentially this:

Is then saved later via Installation.save(). which leads to a

Request URL: https://community.franka.jetzt/classes/_Installation
Request Method: POST

with the content:

appBuild: "1"
appIdentifier: "jetzt.franka.affinity"
appName: "TeamFranka"
appVersion: "1.0"
defaultTeamId: "US2YJKbs7U"
deviceModel: "SM-A405FN"
deviceName: "Galaxy A40"
deviceToken: "d-FOF364QG6bbwZSREDACTEDb8laCVfVdXUOKm_z8-bf4TnvKhWUyzyp5nW2WKOz6Xzj2BFnk30YytZ_p"
deviceType: "android"
installationId: "19ceREDACTED9d02"
_ApplicationId: "REDACTED"
_ClientVersion: "js2.19.0"
_InstallationId: "aa2eREDACTEDd7919c"
_JavaScriptKey: "REDACTED"

And the return I get is what I showed before:

{"updatedAt":"2021-02-21T16:03:05.733Z","defaultTeamId":{"__op":"Delete"},"user":{"__op":"Delete"}}

No id, no where :frowning: .

What do you have in this opts var?

Just the deviceToken:

I noticed that the installation rest api refers to a different endpoint /parse/installation rather than the usual /classes/_Installation. Is there any convenient way to send custom parse-post-requests in the JS SDK (other than cloud-code-function-calls)? Then I could also replace this to use that endpoint instead, if that’s what’s needed…

I believe that’s not the problem as essentially /installations turn out doing the same thing of /classes/_Installation. I guess it is some problem being generated because of the code. Would you mind to simply create and save a new installation and inspect what you see? For instance, I’ve just tested this curl command:

curl -X POST \
  -H "X-Parse-Application-Id: APP_ID" \
  -H "X-Parse-Client-Key: CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"deviceToken": "d-FOF364QG6bbwZSREDACTEDb8laCVfVdXUOKm_z8-bf4TnvKhWUyzyp5nW2WKOz6Xzj2BFnk30YytZ_p", "deviceType": "android", "installationId": "19ceREDACTED9d02"}' \
  https://localhost:1337/parse/installations

And properly received the object id:

{"objectId":"Lpq1s1DfVy","createdAt":"2021-02-26T19:05:44.481Z"}

(having the keys exported), I can see this.

Creating does give the objectId:

❯ curl -X POST \
  -H "X-Parse-Application-Id: $APP_KEY" \
  -H "X-Parse-Client-Key: $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"deviceToken": "d-FOF364QG6bbwZSREDACTEDb8laCVfVdXUOKm_z8-bf4TnvKhWUyzyp5nW2WKOz6Xzj2BFnk30YytZ_p", "deviceType": "android", "installationId": "19ceREDACTED9d02"}' \
  https://parseapi.back4app.com/parse/installations
{"objectId":"2BB010u6WS","createdAt":"2021-02-26T19:39:01.047Z","channels":[],"defaultTeamId":{"__op":"Delete"},"user":{"__op":"Delete"}}%

Just for every following post, doing exactly the same thing, not having any objectId (because in doubt the app was uninstalled, and the only thing available is the same installationId).

❯ curl -X POST \
  -H "X-Parse-Application-Id: $APP_KEY" \
  -H "X-Parse-Client-Key: $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"deviceToken": "d-FOF364QG6bbwZSREDACTEDb8laCVfVdXUOKm_z8-bf4TnvKhWUyzyp5nW2WKOz6Xzj2BFnk30YytZ_p", "deviceType": "android", "installationId": "19ceREDACTED9d02"}' \
  https://parseapi.back4app.com/classes/_Installation
{"updatedAt":"2021-02-26T19:39:20.597Z","defaultTeamId":{"__op":"Delete"},"user":{"__op":"Delete"}}% 

no matter whether I aim for /classes/_Installation or /parse/installations:

❯ curl -X POST \
  -H "X-Parse-Application-Id: $APP_KEY" \
  -H "X-Parse-Client-Key: $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"deviceToken": "d-FOF364QG6bbwZSREDACTEDb8laCVfVdXUOKm_z8-bf4TnvKhWUyzyp5nW2WKOz6Xzj2BFnk30YytZ_p", "deviceType": "android", "installationId": "19ceREDACTED9d02"}' \
  https://parseapi.back4app.com/parse/installations
{"updatedAt":"2021-02-26T19:39:35.691Z","defaultTeamId":{"__op":"Delete"},"user":{"__op":"Delete"}}%           

I guess the other two are updates on the original object, I suppose based on the installationId!?!

I guess this behavior is related to the triggers that you have in place. Would you mind to temporarily
remove the triggers and repeat the tests?

Tried that on localhost, but I am seeing the same behaviour: first comes back with the id, updates do not:

 curl -X POST \
  -H "X-Parse-Application-Id: APPLICATION_ID" \
  -H 'Content-Type: application/json' \
  -d '{"deviceToken": "d-FOF364QG6bbwZSREDACTEDb8laCVfVdXUOKm_z8-bf4TnvKhWUyzyp5nW2WKOz6Xzj2BFnk30YytZ_p", "deviceType": "android", "installationId": "19ceREDACTED9d02"}' \
  http://localhost:1337/parse/classes/_Installation
{"objectId":"QlomNVu6aJ","createdAt":"2021-03-01T11:44:18.062Z"}%                                                              
❯  curl -X POST \
  -H "X-Parse-Application-Id: APPLICATION_ID" \
  -H 'Content-Type: application/json' \
  -d '{"deviceToken": "d-FOF364QG6bbwZSREDACTEDb8laCVfVdXUOKm_z8-bf4TnvKhWUyzyp5nW2WKOz6Xzj2BFnk30YytZ_p", "deviceType": "android", "installationId": "19ceREDACTED9d02"}' \
  http://localhost:1337/parse/classes/_Installation
{"updatedAt":"2021-03-01T11:44:19.397Z"}%                                                                                      
❯  curl -X POST \
  -H "X-Parse-Application-Id: APPLICATION_ID" \
  -H 'Content-Type: application/json' \
  -d '{"deviceToken": "d-FOF364QG6bbwZSREDACTEDb8laCVfVdXUOKm_z8-bf4TnvKhWUyzyp5nW2WKOz6Xzj2BFnk30YytZ_p", "deviceType": "android", "installationId": "19ceREDACTED9d02"}' \
  http://localhost:1337/parse/classes/_Installation
{"updatedAt":"2021-03-01T11:44:21.057Z"}%  

(you can see the beforeSave is not enabled as the results don’t show the changes).

If I switch it from _Installation to Installation (so my own class), each entry gets its own objectId, as expected. So there must be something special happening on _Installation I am not aware of. Do you know where that is in the code? Could you point me it and the rational?

Okay, digging into the parse-server I see that the handling of _Installation is quite complicated. (all links against the 4.4.0-tag, as this the stable server version provided by back4app.com at this time).

iiuc, the following happens: my requests have the same installationId and deviceToken for each request, meaning that on update-requests, the or for installationId would match

which run through all the checks, but as the deviceToken isn’t updated, we just return the objectId (the syntax highlighting is misleading, this is active code)

which is then converted into a regular object-id-based query:

Only because of that, does my beforeSave even get triggered:

Which ends up, tracking the changes made in this beforeSave and remove the objectId from it - because the object-id was in the query constructed by the installationHandle before (line 281).


Whether intended or not in a bigger picture, not returning the objectId is what is being done at a simple objectId-queried-update - which the _Installation-query is converted into. Doesn’t look like there’s any way around this.