phonegap-build / PushPlugin

This repository is deprecated head to phonegap/phonegap-push-plugin
https://github.com/phonegap/phonegap-plugin-push
MIT License
1.31k stars 996 forks source link

Issue with Amazon SNS #322

Open reloaded opened 10 years ago

reloaded commented 10 years ago

I've come across an issue when using Amazon SNS as the push notification delivery. It appears that Amazon is sending the message in a "default" object key that's part of the payload (note that the message and titles of the notification will not be in "message" and "title" properties in the root of the object.

This is not an issue when the app is in the foreground because there's no logic in the Java code that checks for the "message" property first and as a result here's my Javascript that processes the notification when the app is in the foreground (this works perfectly).

navigator.notification.alert(
                            e.payload.default,
                            function() {},
                            'Available Deals',
                            'Ok'
                        );

However, when the app is in the background it checks to see if the "message" property exists, but it doesn't because it's wrapped in an object inside the "default" property (this part doesn't work).

Notice below how the extras bundle contains a property "default" and that's an object with "message" and "title". I've tried everything on my server end to get SNS to send the message and title as root object, but no matter what I try it keeps wrapping it in an object.

09-23 21:51:54.181: V/PushPlugin(31830): extrasToJSON: {"payload":{"default":{"message":"Get 50% off your first indoor skydiving experience if you buy your ticket right now!","title":"IFly Seattle"}},"collapse_key":"do_not_collapse","from":"950756931925","foreground":false,"event":"message","coldstart":false}

Here is the offending code in GCMIntentService.java

@Override
    protected void onMessage(Context context, Intent intent) {
        Log.d(TAG, "onMessage - context: " + context);

        // Extract the payload from the message
        Bundle extras = intent.getExtras();
        if (extras != null)
        {
            // if we are in the foreground, just surface the payload, else post it to the statusbar
            if (PushPlugin.isInForeground()) {
                extras.putBoolean("foreground", true);
                PushPlugin.sendExtras(extras);
            }
            else {
                extras.putBoolean("foreground", false);

                // Send a notification if there is a message
                if (extras.getString("message") != null && extras.getString("message").length() != 0) {
                    createNotification(context, extras);
                }
            }
        }
    }

So, with all of that being said, how can I properly fix this issue in a way (preferably) that won't break after updating the plugin?

RiccardoAmadi commented 9 years ago

How do you use SNS with PGB ? is there a guide somewhere ?

reloaded commented 9 years ago

@riccardoWo Sorry for the very late response... Your question is vague, could you be more specific?

Here is the server side PHP code I used to get this to work.

<?php

require_once __DIR__ . '/vendor/aws-autoloader.php';

use Aws\Sns\SnsClient;

class PushNotification extends RestApi
{
    /**
     * Contains the user's device token/identifier.
     *
     * @access private
     * @var string
     */
    private $token;

    /**
     * Contains the user's device type (Android, iOS, etc.).
     *
     * @access private
     * @var string
     */
    private $platform;

    /**
     * Secret key used for authorization against Amazon AWS API.
     *
     * @var null|string
     */
    protected $accessKey;

    /**
     * Secret key used for authorization against Amazon AWS API.
     *
     * @var null|string
     */
    protected $secretAccessKey;

    /**
     * Amazon AWS ARN for the application we're going to be using for push notifications.
     *
     * @var null|string
     */
    protected $platformArn;

    /**
     * The type of environment the application is running on.
     * For example "development", "stage" or "production"
     *
     * @var string
     */
    protected $environment;

    public function __construct()
    {
        $this->environment = get_cfg_var("environment") ? get_cfg_var("environment") : "production";

        $entity = json_decode(file_get_contents('php://input'));

        $this->token = isset($entity->token) ? trim($entity->token) : "";

        $this->platform = isset($entity->platform) 
            ? strtoupper(preg_replace("/^[^A-Z0-9]$/i", "", $entity->platform)) 
            : "";

        if ($this->token === "")
        {
            $this->error("There was an error identifying your device.");

            return;
        }

        if(!$this->isApns() && !$this->isGcm())
        {
            $this->error("There was an error identifying your device type.");
            return;
        }

        $this->accessKey = get_cfg_var("amazon_access_key")
            ? get_cfg_var("amazon_access_key") 
            : null;

        $this->secretAccessKey = get_cfg_var("amazon_secret_access_key")
            ? get_cfg_var("amazon_secret_access_key") 
            : null;

        $k = sprintf("amazon_%s_application_arn_%s", strtolower($this->platform), $this->environment);
        $this->platformArn = get_cfg_var($k) ? get_cfg_var($k) : null;

        if($this->accessKey === null)
        {
            $this->error("Internal server error. Code: p" . __LINE__);
        }

        if($this->secretAccessKey === null)
        {
            $this->error("Internal server error. Code: p" . __LINE__);
        }

        if($this->platformArn === null)
        {
            $this->error("Internal server error. Code: p" . __LINE__);
        }
    }

    private function isDevelopment()
    {
        return strtolower($this->environment) === "development";
    }

    protected function isApns()
    {
        return $this->platform === "APNS";
    }

    protected function isGcm()
    {
        return $this->platform === "GCM";
    }

    public function sendPush()
    {
        $client = SnsClient::factory(array(
            'key' => $this->accessKey,
            'secret' => $this->secretAccessKey,
            'region' => 'us-west-1'
        ));

        /** @var \Guzzle\Service\Resource\Model $result */
        $result = $client->createPlatformEndpoint(array(
            // PlatformApplicationArn is required
            'PlatformApplicationArn' => $this->platformArn,
            // Token is required
            'Token' => $this->token
        ));
        $endpointArn = $result->get("EndpointArn");

        /** @var \Guzzle\Service\Resource\Model $message */
        $message = $client->publish(array(
            'TargetArn' => $endpointArn,
            'MessageStructure' => 'json',
            'Message' => json_encode(array(
                'default' => $pushMessage,
                'GCM' => json_encode(array(
                    'data' => array(
                        'message' => $pushMessage,
                        'title' => $pushTitle,
                        'notId' => $pushId // The mobile app receives this so you can identify the notification, open a page, etc.
                    )
                ))
            ))
        ));
    }
}

Here is the code I used in the phone gap app. It's partial code, but it will show you the route I took to get this to work without modifying the plugin.

/**
 * Push notification service that provides endpoints for the GCM and APN callbacks.
 */
this.pushNotification = new function() {
    /**
     * The callback that is run for APNS push notifications.
     *
     * @callback pushNotification.onNotificationAPN
     * @param event
     */
    this.onNotificationAPN = function (event)
    {
        console.log("PushNotificationService: APN Response: " + JSON.stringify(event));
        if ( event.alert )
        {
            navigator.notification.alert(event.alert);
        }

        if ( event.sound )
        {
            var snd = new Media(event.sound);
            snd.play();
        }

        if ( event.badge )
        {
            pushNotification.setApplicationIconBadgeNumber(successHandler, errorHandler, event.badge);
        }
    };

    /**
     * The callback that is run for GCM push notifications.
     *
     * @callback pushNotification.OnNotificationGCM
     * @param e
     */
    this.onNotificationGCM = function (e)
    {
        console.log('PushNotificationService: EVENT -> RECEIVED:' + e.event);

        switch( e.event )
        {
            case 'registered':
                if ( e.regid.length > 0 )
                {
                    console.log('PushNotificationService: REGISTERED -> REGID:' + e.regid);
                    // Your GCM push server needs to know the regID before it can push to this device
                    // here is where you might want to send it the regID for later use.
                    console.log("PushNotificationService: regID = " + e.regid);

                    registerLocationServices(self, {platform: "gcm", "token": e.regid});
                }
                break;

            case 'message':
                // if this flag is set, this notification happened while we were in the foreground.
                // you might want to play a sound to get the user's attention, throw up a dialog, etc.
                if ( e.foreground )
                {
                    if(e.payload && e.payload.message && e.payload.title)
                    {
                        navigator.notification.alert(
                            e.payload.message,
                            function() {},
                            e.payload.title,
                            'Ok'
                        );
                    }
                }
                else
                {  // otherwise we were launched because the user touched a notification in the notification tray.
                    if ( e.coldstart )
                    {
                        console.log('Push Notification: --COLDSTART NOTIFICATION--');
                    }
                    else
                    {
                        console.log('Push Notification: --BACKGROUND NOTIFICATION--');
                    }
                }
                break;

            case 'error':
                console.log('Push Notification: ERROR -> MSG:' + e.msg );
                break;

            default:
                console.log('Push Notification: EVENT -> Unknown, an event was received and we do not know what it is');
                break;
        }
    };
}
tomstgeorge commented 9 years ago

Try updating your server send code to the following (you don't need the Message object that you are adding in your structure)

$message = $client->publish(array(
    'TargetArn' => $endpointArn,
    'MessageStructure' => 'json',
    'Default' => $pushMessage,
    'GCM' => json_encode(array(
        'data' => array(
            'message' => $pushMessage,
            'title' => $pushTitle,
            'notId' => $pushId // The mobile app receives this so you can identify the notification, open a page, etc.
        )
    ))
));