Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exception while saving a ParseObject with CurrentInstallation set as a property #2090

Closed
thewizster opened this issue Jun 17, 2016 · 16 comments · May be fixed by LadyK-21/parse-server#8
Closed
Labels
type:bug Impaired feature or lacking behavior that is likely assumed

Comments

@thewizster
Copy link

Experiencing exception while saving a ParseObject with CurrentInstallation set as a property.

Parse.ParseException: at least one ID field (deviceToken, installationId) must be specified in this operation

I have verified that installation ID is set on the CurrentInstallation object before calling SaveAsync.

The code below will save to parse-server on first run when the app is installed. Then, each time the code is run it throws an exception and does not save to parse-server.

Below is the gist of the code. For full code see this repo link:
https://github.com/thewizster/hello-parse-installation/blob/master/HelloParse/HelloViewModel.cs

ParseInstallation installation = ParseInstallation.CurrentInstallation;
var instId = installation.InstallationId.ToString();
var world = new ParseObject("World");
world["message"] = "Hello world!";
world["installation"] = installation; // if removed saves every time without exception
world.SaveAsync();

Steps to reproduce

This can be reproduced using this sample code at:
https://github.com/thewizster/hello-parse-installation

  1. Build and run the app (fresh install on device). ParseObject will save just fine.
  2. Close the app and re-launch. Exception will be thrown and ParseObject will not be saved.
  3. Repeat step 2 as many times as you want.
  4. Uninstall the app and you can start over at step 1.

Expected Results

A new ParseObject (Class: World) should be saved to parse-server and have a pointer to the installation of the device that saved it. Here is an example from the MongoDb of a successful object saved.

{ 
    "_id" : "EImkTziqdr", 
    "message" : "Hello world!", 
    "_p_installation" : "_Installation$iiBDWyIHfc", 
    "_updated_at" : ISODate("2016-06-17T14:15:09.248+0000"), 
    "_created_at" : ISODate("2016-06-17T14:15:09.248+0000")
}

Actual Outcome

When calling SaveAsync on the new ParseObject the save will fail and result in the following exception:

Parse.ParseException: at least one ID field (deviceToken, installationId) must be specified in this operation

Environment Setup

Parse .NET SDK v1.7.0
parse-server v2.2.13
Xamarin.iOS 9.8.0.323
iOS v9.3.2
Visual Studio 2015

  • Server
    • parse-server version: 2.2.13
    • Operating System: Azure/Windows
    • Hardware: Azure cloud
    • Localhost or remote server? (AWS, Heroku, Azure, Digital Ocean, etc): Azure
  • Database
    • MongoDB version: mongod version: 3.0.9
    • Storage engine: ???
    • Hardware: mLab Cloud
    • Localhost or remote server? (AWS, mLab, ObjectRocket, Digital Ocean, etc): mLab

Logs/Trace

App is updating and saving CurrentInstallation upon launch. Works.

�[36mverbose�[39m: POST /parse/classes/_Installation { 'cache-control': 'no-cache',
  connection: 'keep-alive',
  'content-length': '319',
  'content-type': 'application/json',
  host: 'myparseserverhost.azurewebsites.net',
  'max-forwards': '10',
  'x-parse-application-id': 'myAppId',
  'x-parse-client-version': 'net-xamarin-ios1.7.0.0',
  'x-parse-installation-id': '47562ac6-cd5c-4fbc-aaac-afdfa0614beb',
  'x-parse-app-build-version': '1.0',
  'x-parse-app-display-version': '1.0',
  'x-parse-os-version': '9.3.2',
  'x-parse-windows-key': '',
  'x-liveupgrade': '1',
  'x-arr-log-id': '76e42330-2de1-4b30-b81d-b4fca14d4890',
  'disguised-host': 'myparseserverhost.azurewebsites.net',
  'x-site-deployment-id': 'mydeployid',
  'x-original-url': '/parse/classes/_Installation',
  'x-forwarded-for': '1.1.1.1:52456' } {
  "installationId": "47562ac6-cd5c-4fbc-aaac-afdfa0614beb",
  "lastStartedAt": {
    "iso": "2016-06-17T19:18:53.110Z",
    "__type": "Date"
  },
  "deviceType": "ios",
  "timeZone": "America/Chicago",
  "localeIdentifier": "en-US",
  "parseVersion": "1.7.0.0",
  "appVersion": "1.0",
  "appIdentifier": "com.example.hello-parse",
  "appName": "Hello Parse",
  "badge": 0
}

�[36mverbose�[39m: {
  "response": {
    "updatedAt": "2016-06-17T19:18:53.797Z"
  }
}

App attempts SaveAsync on the new ParseObject. Fails

�[36mverbose�[39m: POST /parse/batch { 'cache-control': 'no-cache',
  connection: 'keep-alive',
  'content-length': '80',
  'content-type': 'application/json',
  host: 'myparseserverhost.azurewebsites.net',
  'max-forwards': '10',
  'x-parse-application-id': 'myAppId',
  'x-parse-client-version': 'net-xamarin-ios1.7.0.0',
  'x-parse-installation-id': '47562ac6-cd5c-4fbc-aaac-afdfa0614beb',
  'x-parse-app-build-version': '1.0',
  'x-parse-app-display-version': '1.0',
  'x-parse-os-version': '9.3.2',
  'x-parse-windows-key': '',
  'x-liveupgrade': '1',
  'x-arr-log-id': '5b6fba36-d6bf-4228-9e9f-21787527a605',
  'disguised-host': 'myparseserverhost.azurewebsites.net',
  'x-site-deployment-id': 'mydeploymentid',
  'x-original-url': '/parse/batch',
  'x-forwarded-for': '1.1.1.1:52456' } {
  "requests": [
    {
      "method": "POST",
      "path": "/parse/classes/_Installation",
      "body": {}
    }
  ]
}

�[36mverbose�[39m: {
  "response": [
    {
      "error": {
        "code": 135,
        "error": "at least one ID field (deviceToken, installationId) must be specified in this operation"
      }
    }
  ]
}

Client stacktrace

[0:] Parse.ParseException: at least one ID field (deviceToken, installationId) must be specified in this operation
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Users/builder/data/lanes/3339/39ebb778/source/maccore/_build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/external/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:143 
  at Parse.Internal.InternalExtensions+<>c__DisplayClass13_0`1[TResult].<OnSuccess>b__0 (System.Threading.Tasks.Task t) [0x0003c] in <filename unknown>:0 
  at System.Threading.Tasks.ContinuationResultTaskFromTask`1[TResult].InnerInvoke () <0x1005de120 + 0x00077> in <filename unknown>:0 
  at System.Threading.Tasks.Task.Execute () [0x00016] in /Users/builder/data/lanes/3339/39ebb778/source/maccore/_build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/external/referencesource/mscorlib/system/threading/Tasks/Task.cs:2502 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Users/builder/data/lanes/3339/39ebb778/source/maccore/_build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/external/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:143 
  at Parse.Internal.InternalExtensions+<>c__DisplayClass13_0`1[TResult].<OnSuccess>b__0 (System.Threading.Tasks.Task t) [0x0003c] in <filename unknown>:0 
  at System.Threading.Tasks.ContinuationResultTaskFromTask`1[TResult].InnerInvoke () <0x1005de120 + 0x00077> in <filename unknown>:0 
  at System.Threading.Tasks.Task.Execute () [0x00016] in /Users/builder/data/lanes/3339/39ebb778/source/maccore/_build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/external/referencesource/mscorlib/system/threading/Tasks/Task.cs:2502 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Users/builder/data/lanes/3339/39ebb778/source/maccore/_build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/external/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:143 
  at Parse.Internal.InternalExtensions+<>c__DisplayClass13_0`1[TResult].<OnSuccess>b__0 (System.Threading.Tasks.Task t) [0x0003c] in <filename unknown>:0 
  at System.Threading.Tasks.ContinuationResultTaskFromTask`1[TResult].InnerInvoke () <0x1005de120 + 0x00077> in <filename unknown>:0 
  at System.Threading.Tasks.Task.Execute () [0x00016] in /Users/builder/data/lanes/3339/39ebb778/source/maccore/_build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/external/referencesource/mscorlib/system/threading/Tasks/Task.cs:2502 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Users/builder/data/lanes/3339/39ebb778/source/maccore/_build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/external/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:143 
  at Parse.Internal.InternalExtensions+<>c__DisplayClass13_0`1[TResult].<OnSuccess>b__0 (System.Threading.Tasks.Task t) [0x0003c] in <filename unknown>:0 
  at System.Threading.Tasks.ContinuationResultTaskFromTask`1[TResult].InnerInvoke () <0x1005de120 + 0x00077> in <filename unknown>:0 
  at System.Threading.Tasks.Task.Execute () [0x00016] in /Users/builder/data/lanes/3339/39ebb778/source/maccore/_build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/external/referencesource/mscorlib/system/threading/Tasks/Task.cs:2502 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Users/builder/data/lanes/3339/39ebb778/source/maccore/_build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/external/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:143 
  at Parse.Internal.InternalExtensions+<>c__DisplayClass13_0`1[TResult].<OnSuccess>b__0 (System.Threading.Tasks.Task t) [0x0003c] in <filename unknown>:0 
  at System.Threading.Tasks.ContinuationResultTaskFromTask`1[TResult].InnerInvoke () <0x1005de120 + 0x00077> in <filename unknown>:0 
  at System.Threading.Tasks.Task.Execute () [0x00016] in /Users/builder/data/lanes/3339/39ebb778/source/maccore/_build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/external/referencesource/mscorlib/system/threading/Tasks/Task.cs:2502 

@thewizster
Copy link
Author

No activity. Checking if this was even seen by anyone...

@miracle7
Copy link

miracle7 commented Jun 23, 2016

I am getting this as well when I try to do the following in the Unity SDK:

var installation = ParseInstallation.CurrentInstallation;
installation["user"] = ParseUser.CurrentUser;
installation.SaveAsync();

Would like to know if anyone finds a solution, I'll post here if I figure it out too.

@flovilmart
Copy link
Contributor

@drew-gross, @nlutsenko do you know why we require installationId or deviceToken to be set on Installations? Is that 'really' required?

@miracle7
Copy link

@thewizster Did you ever end up figuring this one out?

I find it strange that it says that an installationId must be specified, yet in the logs you can see "x-parse-installation-id" is actually set, which I assume is the installationId in question.

I have attached my logs below if it will help to debug / replicate.

verbose: POST /parse/classes/_Installation { host: 'torque-dev.herokuapp.com', 
  connection: 'close', 
  'content-type': 'application/json', 
  'x-unity-version': '5.3.5p4', 
  accept: '*/*', 
  'x-parse-session-token': 'r:639326dbbb1fbee8bcf94d16a7e59067', 
  'x-parse-application-id': 'torque-burnout', 
  'x-parse-windows-key': '90b8148d-627c-4c25-8f2e-e3c02acd1f85', 
  'x-parse-installation-id': '13186714-5036-401f-b4a5-18c26a6ea0e7', 
  'x-parse-os-version': 'iPad4,1', 
  'accept-language': 'en-au', 
  'accept-encoding': 'gzip, deflate', 
  'x-parse-client-version': 'net-unity1.7.0.0', 
  'user-agent': 'torqueburnout/98 CFNetwork/758.4.3 Darwin/15.5.0', 
  'x-parse-app-build-version': '98', 
  'x-parse-app-display-version': 'leagueofmonkeys.torqueburnout', 
  'x-request-id': 'd6f5df96-b6b7-4009-be6a-131631b500ec', 
  'x-forwarded-for': '27.32.138.126', 
  'x-forwarded-proto': 'http', 
  'x-forwarded-port': '80', 
  via: '1.1 vegur', 
  'connect-time': '0', 
  'x-request-start': '1469065797649', 
  'total-route-time': '0', 
  'content-length': '94' } { 
  "user": { 
    "__type": "Pointer", 
    "className": "_User", 
    "objectId": "ifX5iXUx4D" 
  }, 
  "language": "English" 
} 
verbose: error: code=135, message=at least one ID field (deviceToken, installationId) must be specified in this operation 

@thewizster
Copy link
Author

@miracle7 I still haven't solved this either. Thanks for posting. Maybe we will get more feedback from Parse contributors. @drew-gross, @nlutsenko, @flovilmart

@flovilmart
Copy link
Contributor

I've recently worked in that area, and I believe I may have an idea on how to solve that issue.

Thanks for posting the logs, that will help craft a reproduction case

@flovilmart
Copy link
Contributor

There is an obvious fix for that issue, before opening the PR, I'd like to get a 2nd opinion with @drew-gross or @nlutsenko

The fix would involve:

  1. plumb the installation-Id from the auth (trivial) raise an error when there is a mismatch between data and headers
  2. ignore the deviceType as it's not being set given the logs provided, and enforced.

fixing the x-parse-installation-id only set on auth (https://github.com/ParsePlatform/parse-server/blob/master/src/RestWrite.js#L574) is mentioned.

I tested against parse.com and the X-Parse-Installation-Id header is not used in that matter. and the body defined installationId will take precedence on the headers

flovilmart added a commit that referenced this issue Jul 22, 2016
@miracle7
Copy link

Thanks @flovilmart. Hopefully we can see a fix for this soon, since it is holding up the release of our app (can't associate user with installation, so can't send push notifications to specific users).

@miracle7
Copy link

FYI our situation is as follows:

Our current production app has been live for about a month (1.8.4), and has parse server enabled.
1.8.4 sets a column called 'deviceId' on the installation after registering for push notifications so we can avoid having the device register for push notifications multiple times.

The next version of our app (1.8.5) allows the user to login with facebook and specify a language to receive push notifications in, but due to this bug where only the first save of CurrentInstallation works the user and language are not associated with the install, since the successful first save is already used up by the previous version when saving the deviceId column.

My workaround for people who start on 1.8.5 is to just save the deviceId at the same time that I associate the user and language so we only save once, but I don't really know what to do for people who are upgrading.

I am thinking maybe I can create a new class to store the association between deviceId and users, then use that to get the appropriate installation, but it would be much easier if I could just save new information to Installations.

@rihadavid
Copy link
Contributor

I've just found a workaround for this issue (although I have no idea why it works). This line solves the issue with deviceToken:

ParseInstallation.CurrentInstallation["deviceToken"] = ParseInstallation.CurrentInstallation.DeviceToken;

but then there's one more issue to solve: "deviceType must be specified in this operation" - you could solve it the same way, unless the SDK told you that you "Cannot change the deviceType property of a _Installation object.". So, you also need to remove "deviceType" from ParseInstallation.cs readOnlyKeys field in the .Net SDK.

Than the rest will be like this:

ParseInstallation.CurrentInstallation["deviceType"] = ParseInstallation.CurrentInstallation.DeviceType;
ParseInstallation.CurrentInstallation["anyCustomValues"] = "anything you need";
ParseInstallation.CurrentInstallation.SaveAsync();

@flovilmart
Copy link
Contributor

The commit has been merged for a while, I believe it should be fixed by now. Can you update to the latest version and let us know if it works correctly?

@reidmweber
Copy link

I'm seeing this issue on parse_server 2.2.19 connecting with the iOS/OS X sdk. Haven't tried 2.2.21 yet. I changed all of my installation saves to saveEventually().

I wasn't getting this error with the hosted version of parse.
I save the installation in AppDelegate here

   func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
      if let installation = PFInstallation.currentInstallation() {
         installation.setDeviceTokenFromData(deviceToken)
         installation.saveEventually()
      }
   }

To associate the user with the installation:

      // Associate the device with a user
      if let installation = PFInstallation.currentInstallation() {
         installation["user"] = user
         let deviceName = DeviceKit.Device()
         installation["iosDevice"] = deviceName.description
         installation.saveEventually()
      }

And then I set a few custom properties to mark when data was refreshed after the user has logged in.

  installation?["workoutsLastUpdatedAt"] = NSDate()
                  installation?.saveEventually()

The server error:

error: Error generating response. ParseError { 
Sep 18 19:48:05 sufferfest-prod app/web.2:    code: 135, 
Sep 18 19:48:05 sufferfest-prod app/web.2:    message: 'at least one ID field (deviceToken, installationId) must be specified in this operation' } code=135, message=at least one ID field (deviceToken, installationId) must be specified in this operation 

@flovilmart
Copy link
Contributor

@reidmweber can you run the server with VERBOSE=1 please and report the full logs relevant to what you're noticing?

@reidmweber
Copy link

reidmweber commented Sep 19, 2016

Wow. that filled up the logs fast. :)

Here is the verbose data:

18 Sep 2016 21:19:59.031 306 <158>1 2016-09-19T04:19:58.903630+00:00 heroku router - - at=info method=POST path="/parse/classes/_Installation" host=app.thesufferfest.com request_id=42c8d62e-e3b1-4305-83e5-9e540b006434 fwd="105.184.181.151" dyno=web.2 connect=0ms service=9ms status=400 bytes=804
» 18 Sep 2016 21:19:59.160 163 <190>1 2016-09-19T04:19:58.886447+00:00 app web.2 - - �[36mverbose�[39m: REQUEST for [POST] /parse/classes/_Installation: {
» 18 Sep 2016 21:19:59.160 116 <190>1 2016-09-19T04:19:58.886466+00:00 app web.2 - - "userLastUpdated": {
» 18 Sep 2016 21:19:59.160 115 <190>1 2016-09-19T04:19:58.886467+00:00 app web.2 - - "__type": "Date",
» 18 Sep 2016 21:19:59.160 131 <190>1 2016-09-19T04:19:58.886468+00:00 app web.2 - - "iso": "2016-09-19T04:19:57.728Z"
» 18 Sep 2016 21:19:59.160 97 <190>1 2016-09-19T04:19:58.886468+00:00 app web.2 - - }
» 18 Sep 2016 21:19:59.160 966 <190>1 2016-09-19T04:19:58.886470+00:00 app web.2 - - } method=POST, url=/parse/classes/_Installation, host=app.thesufferfest.com, connection=close, x-parse-client-version=i1.14.2, accept=*/*, x-parse-session-token=[removed], x-parse-application-id=[removed], x-parse-client-key=, x-parse-installation-id=9be6820e-aeec-439e-8c33-93f3e6f81954, x-parse-os-version=9.3.5 (13G36), accept-language=en-us, accept-encoding=gzip, deflate, content-type=application/json; charset=utf-8, user-agent=The%20Sufferfest/66 CFNetwork/758.5.3 Darwin/15.6.0, x-parse-app-build-version=66, x-parse-app-display-version=3.0, x-request-id=[removed], x-forwarded-for=[removed], x-forwarded-proto=https, x-forwarded-port=443, via=1.1 vegur, connect-time=0, x-request-start=1474258798892, total-route-time=0, content-length=70, __type=Date, iso=2016-09-19T04:19:57.728Z
» 18 Sep 2016 21:19:59.230 150 <190>1 2016-09-19T04:19:58.889176+00:00 app web.2 - - �[31merror�[39m: Error generating response. ParseError {
» 18 Sep 2016 21:19:59.230 106 <190>1 2016-09-19T04:19:58.889177+00:00 app web.2 - - code: 135,
» 18 Sep 2016 21:19:59.230 302 <190>1 2016-09-19T04:19:58.889178+00:00 app web.2 - - message: 'at least one ID field (deviceToken, installationId) must be specified in this operation' } code=135, message=at least one ID field (deviceToken, installationId) must be specified in this operation
» 18 Sep 2016 21:19:59.230 253 <190>1 2016-09-19T04:19:58.890985+00:00 app web.2 - - 105.184.181.151 - - [19/Sep/2016:04:19:58 +0000] "POST /parse/classes/_Installation HTTP/1.1" 400 110 "-" "The%20Sufferfest/66 CFNetwork/758.5.3 Darwin/15.6.0"

Here is the iOS call:
In the User class that inherits from PFUser...

func refreshUser(completion: (user: User?, error: NSError?) -> Void) {
      let installation = PFInstallation.currentInstallation()
      fetchInBackground().continueWithBlock({ (task) -> AnyObject? in
         if task.error == nil {
            installation?["userLastUpdated"] = NSDate()
            installation?.saveEventually()
            completion(user: task.result as? User, error: nil)
         } else {
            completion(user: self, error: task.error)
         }
         return nil
      })
   }

@flovilmart
Copy link
Contributor

That'S perfect! I'll have a look!

@flovilmart flovilmart added pr-submitted type:bug Impaired feature or lacking behavior that is likely assumed and removed in-process labels Sep 19, 2016
@flovilmart
Copy link
Contributor

@reidmweber submitter the RP, once it will be merged you'll be able to test it by pointing parse-server to 'parseplatform/parse-server#latest' in your npm package.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type:bug Impaired feature or lacking behavior that is likely assumed
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants