Who's using what for Push Notifications?

hi, in the interest of science, I’ll put a theoretical fix up here and see if anyone can make it work. My problem is probably my testing environment. I have an un-activated Samsung II and a loaner Android that’s also not on a cell network, and I have a feeling that is stopping me getting the deviceID. That being said, I would think that the following would work (inspired by a post on the Parse forum).

  1. Install Parse Cloud Code like thus: https://www.parse.com/docs/cloud_code_guide. It will install some files on your local, including one called main.js

  2. In Main.js, add the following and deploy it up to your app’s cloud code:

[lua]Parse.Cloud.define(“sendPush”,function(request,response){

      Parse.Cloud.httpRequest({

      method: ‘POST’,

      url: ‘https://android.googleapis.com/gcm/send’,

      headers: {

        ‘Authorization’: ‘key=’ + ‘my_Google_API_Key’,

        ‘Content-Type’: ‘application/json’

      },

      body: {

        registration_ids: request.params.deviceId,

        data: “testing push notification on Android”

      },

      success: function(httpResponse) {

        console.log(“success”);

      },

      error: function(httpResponse) {

        console.log(‘GCM Request failed’ + JSON.stringify(httpResponse));

      }

    

    });

});[/lua]

  1. When you want to invoke the above code from your app, call it like this (you can use a REST API call to call any Parse cloud code function that you stick in min.'s:

–set the globals

APPID = ‘my_Parse_API_Key’

RESTAPIKEY = ‘my_Parse_REST_API_Key’

–pass through the device token you got back from GCM

[lua]

local function sendPush(dt)

        local function sendPushListener()

            print(“sent”)

        end

        headers = {}

        headers[“X-Parse-Application-Id”] = APPID

        headers[“X-Parse-REST-API-Key”] = RESTAPIKEY

        headers[“Content-Type”] = “application/json”

        local params = {}

        commands_json =

             {

                [“deviceId”] = dt

             }        

        postData = json.encode(commands_json)

        

        local params = {}

        params.headers = headers

        params.body = postData

        network.request( “https://api.parse.com/1/functions/sendPush”,“POST”,sendPushListener,params)

       

end[/lua]

This is all theoretical as my devices right now can’t get to first base (e.g. get the deviceID). But if someone else would like to run with this I think it would work. Look forward to your trials!

best,

Jen

Hi Jen, I just tested it real quick since its late here. Can look closer at it again in the morning.

My main question is where we are registering the device ID with Parse? Seems like the deployed function is registering on GCM only?

I got an error, but I will verify my test in the morning though, I was doing this hastily, might have missed something. So please someone else test too.  But I do know it is calling that function in the cloud because before adding that I got a ‘no function’ error.

My result from network request on device:

I/Corona (19568): Parse\_response = { I/Corona (19568): | responseHeaders = { I/Corona (19568): | | Access-Control-Allow-Origin = "\*", I/Corona (19568): | | Content-Length = "51", I/Corona (19568): | | X-UA-Compatible = "IE=Edge,chrome=1", I/Corona (19568): | | Date = "Thu, 20 Jun 2013 14:19:41 GMT", I/Corona (19568): | | Set-Cookie = "\_parse\_session=BAh7BkkiD3Nlc3Npb25faWQGOgZFRiIlMzNlOTJjMjIzYTA3N2VlNmFmM2I3NWE0NWY1OTJjMDk%3D--d043537614b38524d8b9bada3beb9d69c5ec78bf; domain=.parse.com; path=/; expires=Sat, 20-Jul-2013 14:19:41 GMT; secure; HttpOnly", I/Corona (19568): | | Access-Control-Request-Method = "\*", I/Corona (19568): | | X-Runtime = "0.171536", I/Corona (19568): | | X-Android-Sent-Millis = "1371737981559", I/Corona (19568): | | Connection = "keep-alive", I/Corona (19568): | | Cache-Control = "no-cache", I/Corona (19568): | | X-Android-Received-Millis = "1371737981952", I/Corona (19568): | | Status = "400 Bad Request", I/Corona (19568): | | Content-Type = "application/json; charset=utf-8", I/Corona (19568): | | HTTP-STATUS-LINE = "HTTP/1.1 400 Bad Request", I/Corona (19568): | | Server = "nginx/1.2.2", I/Corona (19568): | }, I/Corona (19568): | responseType = "text", I/Corona (19568): | phase = "ended", I/Corona (19568): | bytesEstimated = 51, I/Corona (19568): | response = "{"code":141,"error":"success/error was not called"}", I/Corona (19568): | name = "networkRequest", I/Corona (19568): | bytesTransferred = 51, I/Corona (19568): | status = 400, I/Corona (19568): | url = "https://api.parse.com/1/functions/sendPush", I/Corona (19568): | isError = false, I/Corona (19568): | requestId = "false", I/Corona (19568): },  

ah yes, to register your device with parse, I put in the onNotification listener the following:

[lua]

if event.token ~= nil then

       registerParseDevice(event.token)

else

       print(“no token returned, too bad”)

end

[/lua]

and that function is:

[lua]

local function registerParseDevice(deviceToken)

    

    local environment = system.getInfo( “platformName” )

    if environment == “Android” then 

        deviceType = “android”

    else

        deviceType = “ios”

    end

     

      dt = deviceToken

      print(deviceType)

      print(dt)

      local function parseNetwork

deleted

ah yes, to register your device with parse, I put in the onNotification listener the following:

[lua]

if event.token ~= nil then

       registerParseDevice(event.token)

else

       print(“no token returned, too bad”)

end

[/lua]

and that function is:

[lua]

local function registerParseDevice(deviceToken)

    

    local environment = system.getInfo( “platformName” )

    if environment == “Android” then 

        deviceType = “android”

    else

        deviceType = “ios”

    end

     

      dt = deviceToken

      print(deviceType)

      print(dt)

      local function parseNetworkListener(event)

        print(event.response)        

      end

        headers = {}

        headers[“X-Parse-Application-Id”] = APPID

        headers[“X-Parse-REST-API-Key”] = RESTAPIKEY

        headers[“Content-Type”] = “application/json”

–[[I always put my user into a channel so that I can target them. Note, if you want scheduled pushes, you also need to send timeZone, formatted like ‘America/New\_York’. There’s a google database you can use to input lat/long to get this info]]–

        commands_json =

            {

             [“deviceType”] = deviceType,

             [“deviceToken”] = dt,

             [“channels”] = {“S”…dt}                 

            }        

        postData = json.encode(commands_json)

        

        data = “”

        local params = {}

        params.headers = headers

        params.body = postData

        network.request( “https://api.parse.com/1/installations” ,“POST”, parseNetworkListener,  params)

    

end

[/lua]

I think that is same as doing this Curl command I got from the docs. It can be pasted this into terminal after putting in app and api key (you can have any value in deviceID, same error in any case):

curl -X POST \ -H "X-Parse-Application-Id: 123abc" \ -H "X-Parse-REST-API-Key: 123abc" \ -H "Content-Type: application/json" \ -d '{ "deviceType": "android", "deviceToken": "123", "channels": [""] }' \ https://api.parse.com/1/installations

This gives error: {“code”:114,“error”:“deviceToken may only be set if deviceType is ‘ios’”}  

If you put ios in deviceType and valid ID it works.

my bad. Per the docs

https://www.parse.com/docs/rest#installations

You need to use installationId for Androids

  • deviceType is a required string field that must be set to either “ios” or “android”. It may not be changed once the object is created.
  • installationId is a Parse-generated string identifier that is required for devices with a deviceType of “android” and optional for devices with a deviceType of “ios”. It may not be changed once the object is created and must be unique across all of an app’s installations.
  • deviceToken is an Apple-generated string identifier that is required for devices with a deviceType of “ios”. It may not be changed once the object is created and must be unique across all of an app’s installations.
  • badge is a number field representing the last known application badge for iOS installations.
  • timeZone is a string field representing the system time zone of the device running this installation.
  • channels is an optional array of strings representing the subscriptions of this installation object.

A lot of replies since my last answer :stuck_out_tongue_winking_eye:
It is clear that we can’t use (right now) Parse on Android for pushing because of the installationId (if we don’t have an enterprise license).

I saw jen that you cannot register to GCM and push with the GooglePushNotification example ?

Because I tried it and I got no problem.

I did not make any progress on pushing because I am developing other things. But as I said before, I think that scheduled pushs could be done with GCM registration and Google App Engine (cron jobs + REST Api calls)/Heroku.

Emmanuel, I’m not sure that your above statement is correct. My phone config is just not talking to Google, but if you can get your InstallationID, pass it to parse like I do above and see if you can get the cloud code to fire.

Would love to see if someone can make this work.

thanks everyone for all your help! :slight_smile:

J

Jen, when you say installation ID, do you mean it should be same as device ID?

I tried that and: I get the error “Invalid installation ID”

I would think Its probably something generated by the Android Parse module? 

hi, I had thought that it’s the event.token that is returned by GCM. You pass that to Parse via InstallationId in the json params. Am I wrong? One question I have is whether you really need to register with Parse if you just use the cloud code. You get the InstallationId back from GCM and pass that thru to the cloud code. Did that throw an error?

thanks,

J

According to this you need Parse class natively to generate an installation ID, I think we can safely assume its not the same as deviceID: https://parse.com/questions/how-can-i-get-or-create-the-installationid-string-for-rest-api-pushnotifications

InstallationId has nothing to do with GCM … It is a created field by Parse.

So the event.token that you get back from GCM you just send straight to Parse cloud code, you don’t need to use it to do any Parse installation of your device because Parse isn’t the one sending the push, the cloud code takes care of it via a call back to GCM. 

Not sure I understand, but you can probably send a push with the cloud code using a network request but the device wont be listed under the installation or user section on Parse, so there is not much point I think. 

It would be a setup as Emmanuel has described it, except with Parse instead of Heroku as the external server and you need a cron job to fire it on a schedule. It’s a shame Parse doesn’t have a good way to schedule repeating jobs (you need an external cron job). :frowning:

So, after crying over to have a proper Android / IOS push service and reading all the posts at this topic, I think I found a solution to our pains :slight_smile:

I found Netmera and tried their REST API to register, send push notification to an Android device. Although I didn’t try the IOS part of thier service, but I’m guessing that that part would work.

The good thing about Netmera is their free plan has 1M push messages per month and you can access their REST API, meaning you can run your backend service and use Netmera to send notifications to both platforms. (they’re using GCM)

Also they have quite good list of extra services, in case you’d like to grow your membership.

Here is the working code, which I just editied the Urban Airship code I found somewhere :slight_smile: Also here’s the link for their REST API documentation for push

[lua]

local json = require(“json”)

– Here is the implementation of Push Notification in Corona SDK

local launchArgs = …

local APIKEY = “xxxxxxNetmera APIKeyxxxxxxxxx”

if launchArgs and launchArgs.notification then

    – The code below will only trigger if your app is dead and not active at all    

    – *********************************************************************************    

    native.showAlert( “launchArgs”, json.Encode( launchArgs.notification ), { “OK” } )

    --[[ notification table contains:    

    launchArgs.notification.type - “remote”    

    launchArgs.notification.name - “notification”    

    launchArgs.notification.sound - “sound file or ‘default’”    

    launchArgs.notification.alert - “message specified during push”    

    launchArgs.notification.badge - “5” – badge value that was sent    

    launchArgs.notification.applicationstate - “inactive”    --]]    

    native.showAlert( “Yep notification received”, launchArgs.notification.alert, { “OK” } )

end

– Function to handle Network Traffic Response from Urban Airship

local function netmeraNetworkListener( event )    

    if ( event.isError ) then        

    native.showAlert( “Network error!”, “Error has occured from Netmera”, {“OK”})    

    else        

    native.showAlert( “Netmera”, event.response, {“OK”})    

    end

end

– Function to register device for Netmera Services

local function registerDevice(deviceToken)    

        local headers = {}

        headers[“X-netmera-api-key”] = APIKEY

        headers[“Content-Type”] = “application/json”

        commands_json =

            {

             [“registrationId”] = deviceToken,

             [“platform”] = “ANDROID”,

             [“tags”] = {“tag1”, “tag2”},                

            }        

        postData = json.encode(commands_json)

        print("postData: " … postData)

        data = “”

        local params = {}

        params.headers = headers

        params.body = postData

        network.request( “http://api.netmera.com/push/1.1/registration” ,“POST”, netmeraNetworkListener,  params)

end

– notification listener

local function onNotification( event )    

    if event.type == “remoteRegistration” then        

    registerDevice(event.token)    

    elseif event.type == “remote” then    

    – The code below will only trigger if your app is alive and kicking

    – *********************************************************************************    

    native.showAlert( “remote”, json.encode( event ), { “OK” } )    

    --[[ notification table contains:    

    launchArgs.notification.type - “remote”    

    launchArgs.notification.name - “notification”    

    launchArgs.notification.sound - “sound file or ‘default’”    

    launchArgs.notification.alert - “message specified during push”    

    launchArgs.notification.badge - “5” – badge value that was sent    

    launchArgs.notification.applicationstate - “inactive”    --]]    

    native.showAlert( “Yep notification received”, event.alert , { “OK” } )   

    end

end

    

Runtime:addEventListener( “notification”, onNotification )

[/lua]

Well after some tests and help from Netmera guys, I finally figured out how to use alert and custom fields with netmera :slight_smile:

The code I posted previously is working fine, but I was trying to send some custom data through their REST API and was hitting the wall. We figured out at last and here are some headlines and an example curl request.

  1. Because Alert and Custom fields are not explicitly defined by Google, normally what you send in title or message part of a push alert from Netmera doesn’t include these fields. So you receive the notification but both alert and custom fields will be empty. So this is the first step towards working notifications.

  2. Netmera provides a customjson field that you can put anything in it. So putting a JSON into the custom field with alert and custom fields lets us to receive whatever we’d like to get in Corona.

  3. But of course we’d like to send a json in our custom field, so we need to take care of " with escape character, , here is a sample custom field:

“custom”:{“test”:1}

Btw the alert field shoudl be something like this;

“alert”:“testing alert”

And finally here are some examples;

This is the data part of a Netmera notification REST call;

{“title”: “NOT IMPORTANT FOR CORONA”,“notificationMsg”: “NOT IMPORTANT FOR CORONA”,“registrationId”: “THIS IS THE REGID OF A SPECIFIC DEVICE TO SEND NOTIFICATION”,“customJson”:{“alert”:“THIS IS WHAT YOU GET IN event.alert IN CORONA”, “custom”:"{“boolean”: true,“number”: 123.456,“string”: “some string”,“array”: [true,false, 0, 1, “”, “This is a test.”],“table”: { “x”: 1, “y”: 2 }}"}}

Finally a complete curl commad:

curl -X POST -H “X-netmera-api-key:xxxxNETMERAAPIKEYxxxx” -H “Content-Type:application/json” -d ‘{“title”: “NOT IMPORTANT FOR CORONA”,“notificationMsg”: “NOT IMPORTANT FOR CORONA”,“registrationId”: “THIS IS THE REGID OF A SPECIFIC DEVICE TO SEND NOTIFICATION”,“customJson”:{“alert”:“THIS IS WHAT YOU GET IN event.alert IN CORONA”, “custom”:"{“boolean”: true,“number”: 123.456,“string”: “some string”,“array”: [true,false, 0, 1, “”, “This is a test.”],“table”: { “x”: 1, “y”: 2 }}"}}’ http://api.netmera.com/push/1.1/notification

Couldn’t try the iOS part yet but I’m assuming that part will not be a problem. Ping me if you need help, I’ll try my best. 

Cheers,

Ulas

Ohh also, most probably I’ll be working with Netmera guys to prepare a module that will include some additional functionality. They have some cool notification analytics stuff within their SDK, and they told me maybe we can implement some of them as a LUA module so, also Basic and Pro users can use them.

Glad you got it working for you! :slight_smile: Nice work.