Skip to content
This repository has been archived by the owner on Sep 4, 2020. It is now read-only.

GCM notifications do not work due to wrong notification payload keys #54

Closed
T18970237136 opened this issue Aug 4, 2015 · 11 comments
Closed
Assignees
Labels
Milestone

Comments

@T18970237136
Copy link

Hi,

I'm using this plugin in a Cordova App (using Cordova 5.1.1, android-minSdkVersion=15, android-targetSdkVersion=22) in Visual Studio 2015 to send push notifications with a server. WNS and APNS work without a problem, but I coulnd't get GCM on Android to work. I tested it on an Android 4.0.4 and on an Android 5.1 device.
After debugging the plugin, I found that it seems to use the wrong message keys.

My scenario is the following:

  1. When the Cordova app starts and the deviceready handler is called, I register for notifications (using the Google Project number), and on registration I send the registration ID to the server. I also put an alert in the "notification" event handler to verify that a notification has been received (I have obscured IDs etc. in the sample code):
    function onDeviceReady() {
        // Register for notifications.
        var push = PushNotification.init({
            "android": { "senderID": "xxxxx" },
            "ios": {}, "windows": {}
        });

        push.on('registration', function (data) {
            // Send data.registrationId to the server...
        });

        push.on('notification', function (data) {
            alert("Notification: " + data.message)
        });
    };

The "registration" event handler is successfully called and the registration ID is sent to the server. Some time later the server sends a JSON message to the Google GCM service (https://gcm-http.googleapis.com/gcm/send):

{
  "registration_ids": [
    "APA91bHxxxxxxxxxxxxxxxxxx"
  ],
  "notification": {
    "title": "Hi",
    "message": "Hi, this is a Test"
  }
}

GCM then responds with the following JSON:

{
   "multicast_id": xxxxxxxxxx,
   "success": 1,
   "failure": 0,
   "canonical_ids": 1,
   "results": [
      {
         "registration_id": "APA91bFakxxxxxxxxxx",
         "message_id": "0:xxxxxxxxxxxxxx"
      }
   ]
}

So, GCM sucessfully received the message. However no notifications did show on android when the app was in the background, and when it was in the foreground the alert also did not apper.

I then inserted some debug statements in the GCMIntentService.java file and found that it seems to use the wrong keys for GCM message.
private void onMessage(...) contains the following code:

if (extras.getString("message") != null && extras.getString("message").length() != 0) {
    createNotification(context, extras);
}

So it checks for a "message" key. However, after printing extras.toString(), I got the following:

Bundle[{gcm.notification.message=Hi, this is a Test, gcm.notification.title=Hi, collapse_key=do_not_collapse, from=xxxxxxxxxx}]

So, actually the keys are "gcm.notification.message" instead of "message" and "gcm.notification.title" instead of "title". After I replaced extras.getString("message") with extras.getString("gcm.notification.message") etc. in this class and in the PushPlugin class, everything worked - the phone showed the notification in the notification bar.

Any idea why the notification payload fields in the bundle contain the prefix "gcm.notification." which the plugin does not check? I haven't found a similar report though.

Also it seems strange to me that the plugin expects the message text in a "message" field, whereas the GCM Server Reference (https://developers.google.com/cloud-messaging/server-ref#notification-payload-support) specifies that the text should be in a "body" field in the notification payload.

Thanks!

@macdonst
Copy link
Member

macdonst commented Aug 4, 2015

@T18970237136 can you give me some information on how you send the message to the device? For instance what service do you use to send the message to GCM? I can't seem to reproduce your problem.

@T18970237136
Copy link
Author

Hi,

I'm using a HttpWebRequest in C# to send a simple HTTPS request to GCM (using the Newtonsoft.Json libaray to build the JSON object), using this guide:
https://developers.google.com/cloud-messaging/http
(replace api_key_here with the project's API key and registration_id_here with the device's registrationID:

using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace TestNet46_Console1
{
    class TestSendGcmMessage
    {
        private const string GcmHttpUrl = "https://gcm-http.googleapis.com/gcm/send";
        private const string GcmApiKey = "api_key_here";

        public static void Main(string[] args)
        {
            string registrationID = "registration_id_here";
            SendGCMNotification(registrationID, "Hi", "Hi, this is a Test").Wait();

            Console.WriteLine("Finished");
            Console.ReadKey();
        }

        private static async Task SendGCMNotification(string notifyRegId, string title, string body)
        {
            // Create the notification payload.
            JObject notificationData = new JObject(
                new JProperty("registration_ids", new JArray(notifyRegId)),
                new JProperty("notification", new JObject(
                    new JProperty("title", title),
                    new JProperty("message", body)
                ))
            );
            string contentText = notificationData.ToString(Newtonsoft.Json.Formatting.None);
            byte[] content = Encoding.UTF8.GetBytes(contentText);

            try
            {
                HttpWebRequest req = WebRequest.CreateHttp(GcmHttpUrl);
                req.Method = "POST";
                // Disable expect-100 to improve latency
                req.ServicePoint.Expect100Continue = false;
                req.ContentType = "application/json";
                req.ContentLength = content.Length;
                req.Headers.Add("Authorization", "key=" + GcmApiKey);

                using (Stream s = await req.GetRequestStreamAsync())
                {
                    await s.WriteAsync(content, 0, content.Length);
                }

                // Receive the HTTP response.
                string response;
                using (HttpWebResponse res = (HttpWebResponse)await req.GetResponseAsync())
                {
                    // Read the request body
                    using (TextReader r = new StreamReader(res.GetResponseStream(), Encoding.UTF8, true))
                    {
                        response = await r.ReadToEndAsync();
                    }
                }

                Console.WriteLine("Response: " + response);

                // Parse the response here...
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception: " + ex.ToString());

                if (ex is WebException)
                {
                    WebException webex = (WebException)ex;
                    if (webex.Response != null)
                    {
                        HttpStatusCode status = ((HttpWebResponse)webex.Response).StatusCode;
                        // Check the status code here...
                    }

                }
            }


        }
    }
}

Running this program sends the following HTTP request over a TLS connection:

POST /gcm/send HTTP/1.1
Content-Type: application/json
Authorization: key=api_key_here
Host: gcm-http.googleapis.com
Content-Length: 106
Connection: Keep-Alive

{"registration_ids":["registration_id_here"],"notification":{"title":"Hi","message":"Hi, this is a Test"}}

To test the notifications, I have created a project in the Google Developers Console (https://console.developers.google.com/) and enabled the "Google Cloud Messaging for Android" API. I used the project number as senderID parameter in the Cordova app.

Thanks

@T18970237136
Copy link
Author

I also could reproduce the problem after creating a new project in the Google Developer Console, enabling the "Google Cloud Messaging for Android" API, then creating a key for "Public API access".

I then created a new Cordova app in Microsoft Visual Studio 2015 (Community Edition), changed the cordova version to "5.1.1", changed the Android's Min SDK version to "15" and the Target SDK version to "22". I then added the Device plugin (org.apache.cordova.device) and the Push plugin from the Git repo URL: https://github.com/phonegap/phonegap-plugin-push

Then I changed the code of onDeviceReady in index.js to the following (replacing "xxxxx" with the project number from the Google developer console):

    function onDeviceReady() {
        // Verarbeiten der Cordova-Pause- und -Fortsetzenereignisse
        document.addEventListener( 'pause', onPause.bind( this ), false );
        document.addEventListener( 'resume', onResume.bind( this ), false );

        // TODO: Cordova wurde geladen. Führen Sie hier eine Initialisierung aus, die Cordova erfordert.
        // Register for notifications.
        var push = PushNotification.init({
            "android": { "senderID": "xxxxx" },
            "ios": {}, "windows": {}
        });

        push.on('registration', function (data) {
            // Send data.registrationId to the server...
            alert("RegistrationID: " + data.registrationId);
            console.log("RegistrationID: " + data.registrationId)
        });

        push.on('notification', function (data) {
            alert("Notification: " + data.message)
        });
    };

I run the program on an Android 4.0.4 device which printed the RegistrationID to the console.

Then I used the C# program and replaced "api_key_here" with the new API key from the developers console and "registration_id_here" with the registration ID that has been output to the Android console.

The result was the same: No notification appeared (and the "notification" handler did not fire) due to the plugin not recognizing the "gcm.notification.xxx" keys.

Here is the console output from the Android device (note that I changed some "Log.d()" statements to "Log.i()" in the GCMIntentService.java:

V/PushPlugin( 7270): execute: action=init
V/PushPlugin( 7270): execute: data=[{"android":{"senderID":"xxxxx"},"ios":{},"windows":{}}]
V/PushPlugin( 7270): execute: jo={"senderID":"xxxxx"}
V/PushPlugin( 7270): execute: senderID=xxxxx
(...)
D/PushPlugin( 7270): no icon option
D/PushPlugin( 7270): no iconColor option
(...)
I/PushPlugin_GCMIntentService( 7270): onRegistered: APA91bFsxFxxxxxxxxxxx
I/PushPlugin_GCMIntentService( 7270): onRegistered: {"registrationId":"APA91bFsxFxxxxxxxxxxx"}
(...)
I/PushPlugin_GCMIntentService( 7270): onMessage - context: android.app.Application@2bdf9368
D/PushPlugin( 7270): key = gcm.notification.message
D/PushPlugin( 7270): key = gcm.notification.title
D/PushPlugin( 7270): key = collapse_key
D/PushPlugin( 7270): key = from
D/PushPlugin( 7270): key = foreground
V/PushPlugin( 7270): extrasToJSON: {"additionalData":{"gcm.notification.message":"Hi, this is a Test","gcm.notification.title":"Hi","from":"724495432816","collapse_key":"do_not_collapse","foreground":true}}

This are the contents of the VS's config.xml file:

<?xml version="1.0" encoding="utf-8"?>
<widget xmlns:cdv="http://cordova.apache.org/ns/1.0" xmlns:vs="http://schemas.microsoft.com/appx/2014/htmlapps" id="io.cordova.myappb30ee7" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" defaultlocale="de-DE">
  <name>TestCordovaAndroidGCMNotifications</name>
  <description>A blank project that uses Apache Cordova to help you build an app that targets multiple mobile platforms: Android, iOS, Windows, and Windows Phone.</description>
  <author href="http://cordova.io" email="dev@cordova.apache.org">Apache Cordova Team </author>
  <content src="index.html" />
  <access origin="*" />
  <vs:features />
  <preference name="SplashScreen" value="screen" />
  <preference name="windows-target-version" value="8.1" />
  <platform name="android">
    <icon src="res/icons/android/icon-36-ldpi.png" density="ldpi" />
    <icon src="res/icons/android/icon-48-mdpi.png" density="mdpi" />
    <icon src="res/icons/android/icon-72-hdpi.png" density="hdpi" />
    <icon src="res/icons/android/icon-96-xhdpi.png" density="xhdpi" />
  </platform>
  <platform name="ios">
    <!-- iOS 8.0+ -->
    <!-- iPhone 6 Plus  -->
    <icon src="res/icons/ios/icon-60-3x.png" width="180" height="180" />
    <!-- iOS 7.0+ -->
    <!-- iPhone / iPod Touch  -->
    <icon src="res/icons/ios/icon-60.png" width="60" height="60" />
    <icon src="res/icons/ios/icon-60-2x.png" width="120" height="120" />
    <!-- iPad -->
    <icon src="res/icons/ios/icon-76.png" width="76" height="76" />
    <icon src="res/icons/ios/icon-76-2x.png" width="152" height="152" />
    <!-- iOS 6.1 -->
    <!-- Spotlight Icon -->
    <icon src="res/icons/ios/icon-40.png" width="40" height="40" />
    <icon src="res/icons/ios/icon-40-2x.png" width="80" height="80" />
    <!-- iPhone / iPod Touch -->
    <icon src="res/icons/ios/icon-57.png" width="57" height="57" />
    <icon src="res/icons/ios/icon-57-2x.png" width="114" height="114" />
    <!-- iPad -->
    <icon src="res/icons/ios/icon-72.png" width="72" height="72" />
    <icon src="res/icons/ios/icon-72-2x.png" width="144" height="144" />
    <!-- iPhone Spotlight and Settings Icon -->
    <icon src="res/icons/ios/icon-small.png" width="29" height="29" />
    <icon src="res/icons/ios/icon-small-2x.png" width="58" height="58" />
    <!-- iPad Spotlight and Settings Icon -->
    <icon src="res/icons/ios/icon-50.png" width="50" height="50" />
    <icon src="res/icons/ios/icon-50-2x.png" width="100" height="100" />
  </platform>
  <platform name="windows">
    <icon src="res/icons/windows/Square150x150Logo.scale-100.png" width="150" height="150" />
    <icon src="res/icons/windows/Square150x150Logo.scale-240.png" width="360" height="360" />
    <icon src="res/icons/windows/Square30x30Logo.scale-100.png" width="30" height="30" />
    <icon src="res/icons/windows/Square310x310Logo.scale-100.png" width="310" height="310" />
    <icon src="res/icons/windows/Square44x44Logo.scale-240.png" width="106" height="106" />
    <icon src="res/icons/windows/Square70x70Logo.scale-100.png" width="70" height="70" />
    <icon src="res/icons/windows/Square71x71Logo.scale-240.png" width="170" height="170" />
    <icon src="res/icons/windows/StoreLogo.scale-100.png" width="50" height="50" />
    <icon src="res/icons/windows/StoreLogo.scale-240.png" width="120" height="120" />
    <icon src="res/icons/windows/Wide310x150Logo.scale-100.png" width="310" height="150" />
    <icon src="res/icons/windows/Wide310x150Logo.scale-240.png" width="744" height="360" />
  </platform>
  <platform name="wp8">
    <icon src="res/icons/wp8/ApplicationIcon.png" width="62" height="62" />
    <icon src="res/icons/wp8/Background.png" width="173" height="173" />
  </platform>
  <platform name="android">
    <splash src="res/screens/android/screen-hdpi-landscape.png" density="land-hdpi" />
    <splash src="res/screens/android/screen-ldpi-landscape.png" density="land-ldpi" />
    <splash src="res/screens/android/screen-mdpi-landscape.png" density="land-mdpi" />
    <splash src="res/screens/android/screen-xhdpi-landscape.png" density="land-xhdpi" />
    <splash src="res/screens/android/screen-hdpi-portrait.png" density="port-hdpi" />
    <splash src="res/screens/android/screen-ldpi-portrait.png" density="port-ldpi" />
    <splash src="res/screens/android/screen-mdpi-portrait.png" density="port-mdpi" />
    <splash src="res/screens/android/screen-xhdpi-portrait.png" density="port-xhdpi" />
  </platform>
  <platform name="ios">
    <splash src="res/screens/ios/screen-iphone-portrait.png" width="320" height="480" />
    <splash src="res/screens/ios/screen-iphone-portrait-2x.png" width="640" height="960" />
    <splash src="res/screens/ios/screen-ipad-portrait.png" width="768" height="1024" />
    <splash src="res/screens/ios/screen-ipad-portrait-2x.png" width="1536" height="2048" />
    <splash src="res/screens/ios/screen-ipad-landscape.png" width="1024" height="768" />
    <splash src="res/screens/ios/screen-ipad-landscape-2x.png" width="2048" height="1536" />
    <splash src="res/screens/ios/screen-iphone-568h-2x.png" width="640" height="1136" />
    <splash src="res/screens/ios/screen-iphone-portrait-667h.png" width="750" height="1334" />
    <splash src="res/screens/ios/screen-iphone-portrait-736h.png" width="1242" height="2208" />
    <splash src="res/screens/ios/screen-iphone-landscape-736h.png" width="2208" height="1242" />
  </platform>
  <platform name="windows">
    <splash src="res/screens/windows/SplashScreen.scale-100.png" width="620" height="300" />
    <splash src="res/screens/windows/SplashScreen.scale-240.png" width="1152" height="1920" />
    <splash src="res/screens/windows/SplashScreenPhone.scale-240.png" width="1152" height="1920" />
  </platform>
  <platform name="wp8">
    <splash src="res/screens/wp8/SplashScreenImage.jpg" width="480" height="800" />
  </platform>
  <preference name="android-minSdkVersion" value="15" />
  <preference name="android-targetSdkVersion" value="22" />
  <vs:plugin name="org.apache.cordova.device" version="0.3.0" />
  <vs:plugin name="phonegap-plugin-push" version="1.1.1" src="https://github.com/phonegap/phonegap-plugin-push" />
</widget>

Thanks!

@macdonst
Copy link
Member

macdonst commented Aug 4, 2015

@T18970237136 thanks for the detailed information on how to reproduce your issue. I've never seen the key come across as "gcm.notification.message" before but it should be easy to support this as well.

@macdonst macdonst added the bug label Aug 4, 2015
@macdonst macdonst self-assigned this Aug 4, 2015
@macdonst macdonst added this to the Release 1.2.0 milestone Aug 4, 2015
@smdvdsn
Copy link
Collaborator

smdvdsn commented Aug 6, 2015

Just to add some more information to this issue I had a requirement to support both Urban Airship and Parse push services and they both send the message using different keys. "com.urbanairship.push.ALERT" in the case UA and Parse adds an object under the key "data" with the message key "alert" so in json it looks like { "data": { "alert": "THE MESSAGE" } }

I think that the best solution here is to make the title and message keys configurable. I'm thinking an xpath like DSL so the Parse example above would be configured as "data/alert" and the UA one as "com.urbanairship.push.ALERT". For my requirement it would also need to support passing an array of paths to try.

Let me know if you'd like this and I'll clean up my fork and submit a pull request.

@macdonst
Copy link
Member

@smdvdsn Yeah, I'd be interested in your fork.

@macdonst
Copy link
Member

@T18970237136 can you modify your send code? Change the line:

new JProperty("notification", new JObject(

to

new JProperty("data", new JObject(

and let me know what you get.

When I do a curl like:

curl --header "Authorization: key=<my key>" --header "Content-Type:application/json" https://android.googleapis.com/gcm/send -d "{\"registration_ids\":[\"<my id>"], \"data\": {\"title\": \"Ack\", \"message\": \"The body of the message\"}}" --insecure

I get the notification properly.

@T18970237136
Copy link
Author

@macdonst
Thanks. I did not know that I have to send a data message instead of a notification message - as this plugin is about push notifications, I assumed I should send a notification message to GCM along with "title" and "body" properties, as documented in the GCM reference.
(Maybe it should be documented in the readme how the JSON message that the plugin expects for GCM should look like?)

I cannot test the new code at the moment due to an issue with the device but I assume it will work with the change. I will write a follow-up comment when I could test it.

However I stumbled across another issue on Android, as the javascript does not seem to get an registration error. In the Android logfile I get:

D/GCMBaseIntentService( 1895): handleRegistration: registrationId = null, error = AUTHENTICATION_FAILED, unregistered = null
D/GCMBaseIntentService( 1895): Registration error: AUTHENTICATION_FAILED
E/PushPlugin_GCMIntentService( 1895): onError - errorId: AUTHENTICATION_FAILED

However the push.on('error', function (e) {...} is not called. The GCMIntentService.onError() method only contains a Logging statement. Is the implementation of passing the error to the javascript missing there?

Thanks!

@macdonst
Copy link
Member

@T18970237136 Yeah, I should bump up the documentation more so that folks know what to send to the device. As near as I can tell most services sent the notification in the data object. When you get a chance to test report back.

Regarding your error on AUTHENTICATION_FAILED please create a separate issue so I can track it properly.

@T18970237136
Copy link
Author

Hi,
I checked with an Android device and can confirm that when sending a data message instead of a notification message, the device correctly displays the Notification.
Thanks!

@lock
Copy link

lock bot commented Jun 5, 2018

This thread has been automatically locked.

@lock lock bot locked as resolved and limited conversation to collaborators Jun 5, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Development

No branches or pull requests

3 participants