Using Google Cloud Messaging - Programming Chrome Apps (2014)

Programming Chrome Apps

Appendix C. Using Google Cloud Messaging

Google Cloud Messaging explains how to code a Chrome App to receive messages from Google Cloud Messaging (GCM). You can see the overall architecture in Figure 4-10. This appendix explains how to set up a GCM project, how to code an example app server to communicate with GCM, and how to send GCM messages from Amazon Simple Notification Service. Nothing here is particular to Chrome Apps; everything would work equally well with Android apps or any other app using GCM.

Setting Up a GCM Project

1. Go to the Google Developers Console. You’ll need a Google account if you don’t already have one, but as of this writing, you don’t need to establish billing.

2. Click the Create Project button. In the New Project window that opens, type the project name and then click the Create button, as shown in Figure C-1.

Creating a new project

Figure C-1. Creating a new project

3. After a pause, the window will refresh to show the project number (see Figure C-2), which you should copy and paste into the background.js file of the Chrome App so that it can be passed to chrome.gcm.register as the sender ID (the code is shown in Example Client). (For Android and other apps, you would do things differently, but you’ll still need the registration ID.)

The project number for the new app

Figure C-2. The project number for the new app

4. In the “APIs & auth” section, click APIs and then turn on Google Cloud Messaging for Android (Figure C-3). Do not turn on Google Cloud Messaging for Chrome; that is for the older chrome.pushMessaging API. Some other APIs will be turned on, as well, as shown in Figure C-3, but that’s OK.

Google Cloud Messaging for Android turned on

Figure C-3. Google Cloud Messaging for Android turned on

5. Again, in the “APIs & auth” section, click Credentials. In the “Public API access” section, click CREATE NEW PAGE and then click “Server key” to get the dialog shown in Figure C-4. Click Create to create the API key. You can leave the IP-addresses field blank if you want.

Creating a server key (API key)

Figure C-4. Creating a server key (API key)

6. Copy the displayed API key (see Figure C-5) and paste it into the server script shown in Example App Server (or whatever server app you’ve developed). The app server will need this to communicate with the GCM Connection Server.

The API key

Figure C-5. The API key

This completes the app and project registration with the Google Developers Console, because all you need is the project number (with Google Cloud Messaging for Android turned on) and the API key.

Example App Server

There are several ways to communicate with the GCM Connection Server to send a message, all of them described at developer.android.com/google/gcm. (As of March 2014, this page refers only to Android, not to Chrome Apps, but what’s said there does apply to the chrome.gcm API discussed in Google Cloud Messaging.)

Here I’ll show a PHP script, because PHP is supported by almost all application servers you’re likely to use, it’s simple to use, and it’s understood by most web programmers. (More than 80 percent of websites use it.) If you want, you can write your server in Java, Python, JavaScript (running under Node.js), or any other server-side language. It will still communicate with the GCM Connection Server via HTTP, which all server-side languages support.

Most of the work in the example server isn’t related to GCM—it’s to get some interesting data to send to the Chrome App. For that, it accesses the New York MTA Bus Time server. This service is free, but you’ll need to get an MTA API key (not to be confused with the GCM API key).

There are now three servers, so it’s a good idea to redraw Figure 4-10, which I’ve done in Figure C-6, to show that the app server communicates with the MTA Bus Time server to get bus statuses.

Our example Application Server communicates with two other servers

Figure C-6. Our example Application Server communicates with two other servers

I won’t go into the MTA Bus Time API, of course, except to say that you can get the status of the bus stop at 5th Avenue and 46/47th Street (stop 400516) by using this HTTP query (spread across two lines for readability):

http://api.prod.obanyc.com/api/siri/stop-monitoring.json

?key=<MTA_KEY>&MonitoringRef=400516

The result comes back as a complex object (converted from JSON to a PHP object, as we’ll see), which you have to study to find the relevant data—in this case, only the buses within one stop of 46/47th Street. (As all New Yorkers know, 5th Avenue is one-way south, so all buses go the same direction.)

After data is extracted from the returned object, a typical status message looks like this:

12:45:50 -- M4 at stop; M4 1 stop away@5 AV/WEST 47 - 46 ST

That message is then sent to the GCM Connection Server, also via HTTP, as we’re about to see.

Recall from Google Cloud Messaging that a Chrome App that wants to receive GCM messages must send its registration ID to the server. The server, in gcmv2-bus.php, checks for the presence of a regID query parameter and, if it’s nonempty, saves the registration ID in the regID-bus.datafile. If the parameter is missing, the server begins broadcasting status messages. Here’s the first part of the PHP code:

define('REG_ID_FILE', 'regID-bus.data');

define('API_KEY', 'AIzaSy...CaqPk');

define('MTA_KEY', '91474b6...3f95');

$url = "http://api.prod.obanyc.com/api/siri/stop-monitoring.json?key=" .

MTA_KEY . "&MonitoringRef=400516";

if (!empty($_REQUEST['regid']))

store_regID($_REQUEST['regid']);

else

broadcast();

function get_regIDs() {

if (file_exists(REG_ID_FILE))

return unserialize(file_get_contents(REG_ID_FILE));

return array();

}

function store_regID($regID) {

$a = get_regIDs();

$a[$regID] = 1;

file_put_contents(REG_ID_FILE, serialize($a));

}

If you don’t know PHP, the details aren’t important, but you do need to know that the get_regIDs function returns an array of registration IDs to which messages are to be sent.

The broadcast function queries the MTA Bus Time server every 15 seconds:

function broadcast() {

$num_sent = 0;

for ($n = 0; $n < 20; ) {

$regIDs = '';

foreach (get_regIDs() as $k => $v)

$regIDs .= ',"' . $k . '"';

$regIDs = substr($regIDs, 1);

if (send_message(API_KEY, $regIDs))

$n++;

sleep(15);

}

}

It gets a fresh array of registration IDs each time, too, in case a client registered while it was looping. (Such registration would invoke a different execution of the script, but I didn’t bother programming any locks to prevent simultaneous access to the file of registration IDs.) The code at the start of the for loop assembles the registration IDs into a comma-separated list, which is what send_message needs, something like this:

"APA91bHSH-NzFPIZvl15B",

"APA91bFxX-lplMoOtpuNw",

"APA91bGNU-yNCMkeK1PQu"

NOTE

In working out this example server, I somehow got a couple of server instances running on the server computer with no way to kill them because they were running on a shared host on which I have very limited permissions. That’s why the app is designed to terminate after 20 statuses are sent; a real server would keep running until it’s terminated, perhaps with a separate management utility, but that’s overkill for a book example.

Here’s the send_message function, which does one query of the MTA Bus Time server and sends the message to the GCM Connection Server, but only if the message is nonempty (there is a bus within one stop) and it’s different from the last message sent:

function send_message($apiKey, $regIDs)

{

global $url;

global $prev;

$response = send_request(false, $url,

array("Content-Type: application/json"));

$x = json_decode($response);

$sd = $x->Siri->ServiceDelivery;

$rt = $sd->ResponseTimestamp;

$msv = $sd->StopMonitoringDelivery[0]->MonitoredStopVisit;

$s = '';

foreach ($msv as $k => $v) {

$mvj = $v->MonitoredVehicleJourney;

$dist = $mvj->MonitoredCall->Extensions->Distances->PresentableDistance;

$stopName = $mvj->MonitoredCall->StopPointName;

switch ($dist) {

case 'at stop':

case 'approaching':

case '1 stop away':

$s .= "; {$mvj->PublishedLineName} {$dist}";

}

}

if (!empty($s) && $s != $prev) {

$prev = $s;

$msg = substr($rt, 11, 8) . " -- " . substr($s, 2) . "@$stopName";

echo "<hr>$msg";

$data = <<<EOT

{

"data": {

"message": "$msg"

},

"registration_ids": [$regIDs],

"delay_while_idle": true,

"time_to_live": 600

}

EOT;

send_request(true, "https://android.googleapis.com/gcm/send",

array("Content-Type: application/json", "Authorization: key=$apiKey"),

$data);

}

}

Expressions such as $mvj->MonitoredCall->StopPointName in the code refer to sub-objects within the object returned by the MTA Bus Time server. (It took me an hour or so of playing around to figure out where the data was that I wanted.) If the status is to be sent (!empty($s) && $s != $prev), it’s prefixed with the time, suffixed with the stop name, and then sent to the GCM Connection Server as part of a data object that also includes the array of registration IDs.

Finally, send_request uses cURL to send HTTP requests by using GET or POST:

function send_request($post, $url, $headers, $postText = null) {

$ch = curl_init();

curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

curl_setopt($ch, CURLOPT_URL, $url);

curl_setopt($ch, CURLOPT_POST, $post);

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

if ($postText)

curl_setopt($ch, CURLOPT_POSTFIELDS, $postText);

$response = curl_exec($ch);

curl_close($ch);

return $response;

}

For uploading registration IDs, this PHP file is executed from the Chrome App’s background.js page, as we saw in Example Client:

function sendRegistrationID(registrationID) {

// Should use https

Ajax.ajaxSend("http://basepath.com/servers/gcmv2-bus.php?regid=" +

registrationID, 'json',

function (status, response) {

if (status == 200)

chrome.storage.local.set(

{

registered: true

}

);

else

console.log('Error sending registrationID');

}

);

}

For broadcasting, I just executed the server from a web browser, as shown in Figure C-7. That’s OK for an example, but for a real application you’d want to start the app server directly from the web server, perhaps by using cron.

Starting the app server from a browser

Figure C-7. Starting the app server from a browser

Using Amazon Simple Notification Service

If you just want to test a GCM-receiving Chrome App without going to the trouble of developing an app server, you can send messages directly with Amazon Simple Notification Service (SNS). (Interestingly, as of this writing Google doesn’t have a similar feature.) Here’s what you do:

1. If you don’t already have one, set up an Amazon Web Services (AWS) account at aws.amazon.com. You might need to establish a billing account, but the cost for sending a few messages is either free or only pennies, so don’t worry about it.

2. Go to the SNS console and click the “Add a New App” menu item. Give the app a name (see Figure C-8), choose GCM as the Push Platform, and enter the GCM API key that you got from the Google Developers Console (not from Amazon).

3. When the app is created, click the Add Endpoints button and enter the registration ID from a Chrome App, as shown in Figure C-9.

NOTE

There’s probably an API for uploading the registration ID directly from the Chrome App, but I didn’t bother. I just copied it from the regID-bus.data used by the PHP app server and pasted it into the SNS dialog. Or, you could use console.log to display it from the Chrome App and copy it from there. Note that SNS refers to the registration ID as a Device Token.

Creating a new SNS app

Figure C-8. Creating a new SNS app

Creating an SNS endpoint

Figure C-9. Creating an SNS endpoint

4. After the endpoint is created, check it and click the Publish under Endpoint Actions menu item. Type a message, as demonstrated in Figure C-10, and click the Publish Message button to send the message.

Publishing a message with SNS

Figure C-10. Publishing a message with SNS

5. Because the message I typed came from SNS, not the GCM Connection Server, it wasn’t formatted as the Chrome App expected it to be (no message property), so the app didn’t know what to do with it. Nonetheless, the console for the background script proves that it arrived, as illustrated in Figure C-11.

Console log from Chrome App showing a message

Figure C-11. Console log from Chrome App showing a message