GCM - Chrome App/Extension間での通知を受け取る方法

* 翻訳対象のページ
  - [Implementing GCM Client on Chrome]
(https://developers.google.com/cloud-messaging/chrome/client)
  - 2015/06/02-05

# Implementing GCM Client on Chrome

### 概要

GCM はChromeを含む異なるプラットフォームのためのメッセージングサービスを提供しています。
Chromeのインスタンスがサーバからのメッセージの送受信ができるようになります。
Chrome.GCM API は Chrome apps または Chrome拡張 が GCMサービスに接続を許可します。
APP や 拡張 が起動していなくてもChrome.GCM APIは動いています。
例えば、カレンダーアプリが開いていない場合でもカレンダー更新されたことをユーザに通知することが可能になります。

あなたは Google APIs サービス利用規約 と Google Cloud Platform サービス利用規約の両方を使うために、各利用規約に同意しなければいけません。

GCMの導入には Client側の実装とサーバ側の実装が必要になります。
サーバ側の実装のより詳しい情報は[GCM接続サーバについて](https://developers.google.com/cloud-messaging/server)を参照ください。

# API Projectと証明書のセットアップ

Google API Projectを作るためには

1. Google Developers Consoleを開きます
2. もしAPI Projectを作ったことがなければ、「Create Project」をクリックしましょう
3. Project名を記入し「Create」をクリックします。一度プロジェクトを作っていると、あなたのProject IDとProject Numberがページに表示されます。`Project Number: 670330094152`のように
4.あなたのProject Numberを書き留めましょう。後ほどGCM Sender IDとして利用することになります。

## GCMサービスの有効化

GCMサービスを有効にするためには

1. サイドバー左の「APIs & auth」を選択します
2. 表示されているAPI一覧内の、「Gogole Cloud Messaging for Android」のトグルをONにします

# Chrome App または Chrome 拡張のセットアップ

### manifestへのパーミッション追加

GCMサービスを使用するためには 'manifest.json` 内にGCM パーミッションを宣言する必要があります。

```
"permissions": [
  "gcm", ... // Other permissions, like "storage"
]
```

下記動作中のサンプルコードでは、Chrome.storage APIを通してデータ保持するために、Storage パーミッションも要求していることに注意してください。

## Chromeアプリ,拡張を作る


### GCM registration tokenの取得

メッセージを受信するには事前にGCM接続サーバに登録する必要があります。
chrome.gcm.registerを呼び出し、Google Developersコンソール上のProject number によって識別される送信先リストを渡すことで利用準備が完了です。
Chrome Appまたは拡張のコールバック関数において、`chrome.runtime.lastError` プロパティに値が格納されるか否かで確認できるGCM登録の成功可否を確認すべきです。
もし登録が成功していれば、アプリまたは拡張でregistration tokenを受け取り、HTTPS経路などの安全な方法であなたのサーバに送り返す必要があります。
一方、アプリまたは拡張が`chrome.runtaime.lastError` によってわかるエラーはGCMへリトライすることで対処すべきです。
もし、アプリまたは拡張が送信者が異なるメッセージを受信したい場合は、 もう一度新しい送信者リストと共にchrome.gcm.register をコールすることで、registration tokenが返却されます。

```
function registerCallback(registrationId) {
  if (chrome.runtime.lastError) {
    // When the registration fails, handle the error and retry the
    // registration later.
    return;
  }

  // Send the registration token to your application server.
  sendRegistrationId(function(succeed) {
    // Once the registration token is received by your server,
    // set the flag such that register will not be invoked
    // next time when the app starts up.
    if (succeed)
      chrome.storage.local.set({registered: true});
  });
}

function sendRegistrationId(callback) {
  // Send the registration token to your application server
  // in a secure way.
}

chrome.runtime.onStartup.addListener(function() {
  chrome.storage.local.get("registered", function(result) {
    // If already registered, bail out.
    if (result["registered"])
      return;

    // Up to 100 senders are allowed.
    var senderIds = ["Your-Sender-ID"];
    chrome.gcm.register(senderIds, registerCallback);
  });
});

```

もしアプリまたは拡張が異なるプロフィールかつまたは異なるクロムインスタンス内にインストールされている場合、
それぞれ、異なるregistration tokenが受信されます。

アプリまたは拡張は　registration tokenを無効にするために `gcm.unregister` が用意されています。
この無効化処理はアプリまたは拡張が、次のメッセージを受信したくない時や、registration tokenが危険にさらされる疑いがある場合などのレアケースにのみ、行うべきです。

```
function unregisterCallback() {
  if (chrome.runtime.lastError) {
    // When the unregistration fails, handle the error and retry
    // the unregistration later.
    return;
  }
}

chrome.gcm.unregister(unregisterCallback);
```

ユーザがあなたのアプリまたは拡張をアンインストールした場合、自動的にGCMサービスの登録解除が行われます。

### ダウンストリームメッセージの受信

サーバがユーザに対してメッセージを送りたい場合、GCM接続サーバについてに記載されている2種類の接続手段の内の1つを用いてGCM接続サーバに接続する必要があります。
GCM接続サーバがメッセージを受信にした際、ChromeアプリまたはChrome拡張の全てのChromeインスタンスのregistration IDの中から1つに対してメッセージを届けます。
Chromeインスタンス中に一つ以上のProfilesが入っているChromeアプリまたはChrome拡張がインストールされている場合でも
ユニークなRegistration tokenに基いき、全てのアプリまたは拡張で個別にメッセージを受信することが可能です。

`chrome.gcm.onMessage` eventを通してサーバからメッセージが配信されます。
あなたはのChromeアプリまたはChrome拡張はこのeventを受診するためのハンドラーとして登録する必要があります。

```
chrome.gcm.onMessage.addListener(function(message) {
  // A message is an object with a data property that
  // consists of key-value pairs.
});
```

Chromeが動いている限り、ChromeアプリまたはChrome拡張が動いていなくても、メッセージが配達されるため起動されます。

### アップストリームメッセージの送信

サーバからクライアントにPushメッセージの送信に加えて、GCMはClientからサーバにアップストリームメッセージ送信に対応しています。
あなたのサーバはXMPP 接続サーバ実装ドキュメント内に記載されている GCM Cloud 接続サーバに接続するための準備をする必要があります。
アップストリームでメッセージを送信するために、ChromeアプリまたはChrome拡張は `chrome.gcm.send` 下記オブジェクトを添えてコールする必要があります。

* Message ID:
  * 失敗時にキューイングされ配達される際に用いる各メッセージを識別するための識別子。Message IDは文字列で定義されますが、アプリまたは拡張のが起動された後も含めたライフタイムを通してユニークであるIDにしてください。もし同じMessage Idを使うと、以前のメッセージが上書きされる可能性があります。もしMessage Id作成時にAuto-increment カウンターが使われていた場合使っていた場合、あなたのChromeまたはChrome拡張は `chrome.storage API` を用いてカウント値の永続化とアプリ/拡張が再読み込みされた時に復元する必要があります。
* Destination ID:
  * サーバを識別するためのID。Google Developers Console のプロジェクトナンバーの末尾に `@gcm.googleapis.com` 加えたものです。
* Data:
  * 4KB上限のString-Stringのキーバリューの配列で構成されたデータ
* TTL (Time-to-live):
  * このプロパティは0秒から86400秒(GCMのメッセージ保存期間の応答時間上限と同一の1日)の間で定義する必要があります。もしこのプロパティを定義していない場合、最大値(1日)がデフォルト値としてセットされます。TTLに0を指定した場合、即時にメッセージ送信を試みますが、送信失敗時にはメッセージが破棄されます。

runtime errorなしに呼び出される`chrome.gcm.send` 内のコールバックが呼ばれた際、既にGCM接続サーバにメッセージが配信されていないことを意味しているわけではありません。
むしろメッセージ配信のためキューイングされたことを意味します。あなたのアプリまたは拡張は `chrome.runtime.lastError`をチェック、ハンドルすべきです。
返却される可能性のあるErrorコードを参照ください。

TTL期間内にメッセージが目的のサーバに到達失敗する例として、ネットワークエラー起因の場合は`chrome.gcm.onSendError`が発生します。
あなたのChromeアプリまたはChrome拡張はイベントをListenし反応することができます。(たとえば、再送信するなど)
返却される可能性のあるエラーコードを参照ください。

```
// Substitute your own sender ID here. This is the project
// number you got from the Google Developers Console.
var senderId = "Your-Sender-ID";

// Make the message ID unique across the lifetime of your app.
// One way to achieve this is to use the auto-increment counter
// that is persisted to local storage.

// Message ID is saved to and restored from local storage.
var messageId = 0;
chrome.storage.local.get("messageId", function(result) {
  if (chrome.runtime.lastError)
    return;
  messageId = parseInt(result["messageId"]);
  if (isNaN(messageId))
    messageId = 0;
});

// Sets up an event listener for send error.
chrome.gcm.onSendError.addListener(sendError);

// Returns a new ID to identify the message.
function getMessageId() {
  messageId++;
  chrome.storage.local.set({messageId: messageId});
  return messageId.toString();
}

function sendMessage() {
  var message = {
    messageId: getMessageId(),
    destinationId: senderId + "@gcm.googleapis.com",
    timeToLive: 86400,    // 1 day
    data: {
      "key1": "value1",
      "key2": "value2"
    }
  };
  chrome.gcm.send(message, function(messageId) {
    if (chrome.runtime.lastError) {
      // Some error occurred. Fail gracefully or try to send
      // again.
      return;
    }

    // The message has been accepted for delivery. If the message
    // can not reach the destination, onSendError event will be
    // fired.
  });
}

function sendError(error) {
  console.log("Message " + error.messageId +
      " failed to be sent: " + error.errorMessage);
}
```

## Advanced Topics

### メッセージ削除時のイベント

GCMはサーバからクライアントに対して送信した折りたためないメッセージを100個上限としている。
その後全てのメッセージはGCMから削除され、Event `chrome.gcm.onMessagesDeleted`　が呼ばれ、Clientに対して失敗した旨を知らせます。
Chrome AppまたはChrome拡張はアプリケーションサーバが削除メッセージを復元できるよう同期する反応をする必要があります。

```
chrome.gcm.onMessagesDeleted.addListener(messagesDeleted);

function messagesDeleted() {
  // All messages have been discarded from GCM. Sync with
  // your application server to recover from the situation.
}
```

### Collapsible messages(原文)

GCM messages are often a tickle, telling the app or extension to contact the server for fresh data.

In GCM, it's possible to create collapsible messages for this situation, wherein new messages replace older ones. When a collapse key is provided and multiple messages are queued up in the GCM connection servers for the same user, only the last one with any given collapse key is delivered to your app or extension.

As a result the message object passed to the chrome.gcm.onMessage event will contain a collapseKeyfield.

## Error reference (原文)

An error could occur when a gcm API function is called. Your app or extension should check chrome.runtime.lastError for more information in your callback. The error code will also be passed as a parameter to chrome.gcm.onSendError event.

Here's a brief summary of the gcm errors:

* Function was called with invalid parameters: this could happen when gcm functions are called with bad parameters.
* Asynchronous operation is pending: this could happen when certain gcm function is called again without waiting for the callback passed in previous function to be called.
* Network error occurred: this could happen when GCM connection server fails to reach due to network problem, like losing Internet connection.
* Server error occurred: this could happen when GCM connection server fails to reach due to server problem, like server busy.
* Time-to-live exceeded: this could happen when a message could not be delivered within the specific time-to-live period.
* Unknown error occurred: this could happen due to any other internal errors.