必要条件

この記事に必要な予備知識

この記事は、中級レベル以上のPhoneGapの開発経験があることを前提としています。

ユーザーレベル

中級

原文 作成日: 2012/11/19
Apple push notifications with PhoneGap

この記事では、PhoneGapモバイルアプリケーションでApple Push Notifications(APNs)を使用する方法について説明します。プッシュ通知は、何かをユーザーに通知するためにサードパーティのサーバーから送信される点がローカル通知とは異なります。ローカル通知の場合は、アプリケーションによってスケジュールされ、デバイス自体で実行され、サーバーとのインタラクションは行われません。

例えば、Facebookからは、他のユーザーが自分を友人として追加したというプッシュ通知を受信します。また、Words With Friendsをプレイしているときには、自分の番が来たことを知らせるプッシュ通知を受信します。ローカル通知の例としては、タスクの日時を設定するTo-Doアプリケーションで、アラームとして特定の時刻または時間間隔でポップアップ表示されるアラートがあります。指定された時刻になると、アラートがポップアップ表示され、ユーザーに知らせます。エンドユーザーにとっては、サウンドやその他の機能が関連付けられたアラートがポップアップ表示される点で、どちらの通知も同じように見えます。しかし、開発の観点からすると、この2つの通知は大きく異なります。

iOS用のローカル通知プッシュ通知の両方に対応したCordova/PhoneGapプラグインも存在しますが、このシリーズではプッシュ通知向けの開発方法を重点を置いて説明していきます。Android向けのプッシュ通知の開発に関心を持たれている場合、この記事でプッシュ通知の概念を知ることはできますが、その設定とプロセスは若干異なるので、別の記事で説明することにします。

APNsを設定するためのプロセスは、最初は少し難しいと感じられるかもしれませんが、その用途は無限に広がっているので、時間をかけるだけの価値はあります。この記事では、わかりやすいサンプルコードを使用して、設定およびアプリケーションとサーバー側での処理を含めた全体的なプロセスを理解できるようにすることを目指しています。

APNワークフローの説明

最初に、Apple Push Notifications(APNs)のワークフローを理解しておく必要があります。

  • アプリケーションは、起動時にApple Push Notification Serviceと通信し、プッシュ通知を受信するための承認を受けます。
  • Appleは一意のトークンを返します。それ以降のプッシュ通知を受信するための通信では、このトークンを使用する必要があります。
  • アプリケーションはトークンをサードパーティのサーバー(自社のサーバーでも、Urban Airshipなどの他のプロバイダーでも可)に送信します。サードパーティのサーバーは、アプリケーションへの通知が必要になる、以降のイベントのためにトークンを格納します。

画像作成:Ray Wenderlich氏

SSL証明書とプロビジョニング

この手順に怖気づかないでください。実際に行えば、それほど難しくありません。この手順について詳しくは、Ray Wenderlich氏の「Apple Push Notification Services Tutorial: Part 1 of 2」(Apple Push Notification Servicesチュートリアル:パート1/2)を参照してください。この記事では、この手順で使用する画面のスクリーンショットを使用して、このプロセスについて詳しく説明しています。

ここでは、アプリをプッシュ通知に対応させるための作業の一般的な概要を示します。最初に、App ID(com.mysite.myapp)を使用して、Apple iOS Provisioning Portalでアプリケーションをプッシュ通知に対応できるようにし、プッシュ対応のアプリケーションIDを含むプロビジョニングプロファイルを使用してアプリケーションに署名する必要があります。次に、AppleのPush Notification Serverと安全に通信できるように、App IDをSSL証明書と関連付ける必要があります。このポータルでApp IDを設定するときに、ウィザードの指示に従って、App IDに関連付けられ前述の目的で使用されるSSL証明書を作成できます。App IDとの関連付けによって、使用するサーバーからAppleのAPN Serverに送信された通知は、IDが一致するアプリケーションにのみ送信されます。

証明書の処理が完了したら、有効なプッシュ通知を含むこの新しいApp ID用の新しいプロビジョニングファイルをダウンロードします。次に、このファイルをXCodeにドラッグし、Code Signing(コードの署名)画面(プロジェクトの「Build Settings(Build の設定)」にある)で、アプリケーション用にこのプロビジョニングプロファイルが選択されていることを確認します。

アプリケーションの設定

この記事では、PhoneGap/Cordovaを含め、プロジェクトの作成方法の知識があることを前提としています。プロジェクトの作成方法については、PhoneGap/Cordova Webサイトの「Getting Started with iOS」(iOSの概要)を参照してください。これで、HTML/JavaScript/PhoneGapアプリケーションのコーディングを開始する準備ができたので、以下の手順に従って設定してみましょう。

  1. GitHubから最新のPhoneGap Push Notificationsプラグインを入手します。
  2. PushNotificationフォルダーをXCodeのPluginsフォルダーにドラッグ&ドロップします。スクリーンショットに示されているように、コピーオプションとして「Create groups for any added folders(追加フォルダのグループを作成)」を選択します。

  1. XCodeからFinderに移動して、PushNotification.jsファイルをwww\plugins(wwwフォルダーのpluginsサブフォルダー)にコピーします。このファイルは、XCodeで自動的に表示されます。
  2. 次のように、HTMLファイルにPushNotification.jsファイルを参照するscriptタグを追加します。

    <script src="js/plugins/PushNotification.js"></script>
  3. プラグインのキーと値をCordova.plist(このファイルはプロジェクトのルートのResourcesフォルダーにある)に追加します。

Cordova PushNotificationプラグインは、HTML/JSファイルから使用して基になるネイティブコードを操作し、プッシュ通知の登録や受信を処理するために使用できる、優れたJavaScript APIを提供します。提供される関数には、次のようなものがあります。

  • registerDevice()
  • setApplicationIconBadgeNumber()
  • getRemoteNotificationStatus()
  • getPendingNotifications()

ただし、このJavaScript APIを使用するには、最初に、特定のプッシュ通知コードへの橋渡しをするものを、ネイティブアプリケーションコードに追加する必要があります。アプリケーション用のAppDelegateクラス(プロジェクトのClassesフォルダーにある)は、アプリケーション全体のイベント(アプリケーションの起動、終了など)のハンドラーを実装します。通知の処理のために使用できるイベントもあります。このようなイベントの一覧については、通知の処理のリファレンスを参照してください。

ただし、これらのメソッドはデフォルトでは実装されていないため、これらのメソッドをサポートするには、コードハンドラーを追加し、プラグインコードでインクルードされたPushNotification.mクラスにデリゲートさせる必要があります。追加するコードはプラグインのREADMEファイルに示されていますが、ここでも説明します。基本的に3つのイベントを処理します。

  • didReceiveRemoteNotification
  • didRegisterForRemoteNotificationsWithDeviceToken
  • didFailToRegisterForRemoteNotificationsWithError

これらの通知イベントを処理するには、次のコードブロックを、AppDelegate.mクラスの@endの前に追加します。

/* START BLOCK */

#pragma PushNotification delegation

 

- (void)application:(UIApplication*)app

didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken

{

    PushNotification* pushHandler = [self.viewController getCommandInstance:@"PushNotification"];

    [pushHandler didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];

}



- (void)application:(UIApplication*)app didFailToRegisterForRemoteNotificationsWithError:(NSError*)error

{

    PushNotification* pushHandler = [self.viewController getCommandInstance:@"PushNotification"];

    [pushHandler didFailToRegisterForRemoteNotificationsWithError:error];

}



- (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo

{

    PushNotification* pushHandler = [self.viewController getCommandInstance:@"PushNotification"];

    NSMutableDictionary* mutableUserInfo = [userInfo mutableCopy];



    // Get application state for iOS4.x+ devices, otherwise assume active

    UIApplicationState appState = UIApplicationStateActive;

    if ([application respondsToSelector:@selector(applicationState)]) {

        appState = application.applicationState;

    }



    [mutableUserInfo setValue:@"0" forKey:@"applicationLaunchNotification"];

    if (appState == UIApplicationStateActive) {

        [mutableUserInfo setValue:@"1" forKey:@"applicationStateActive"];

        [pushHandler didReceiveRemoteNotification:mutableUserInfo];

    } else {

        [mutableUserInfo setValue:@"0" forKey:@"applicationStateActive"];

        [mutableUserInfo setValue:[NSNumber numberWithDouble: [[NSDate date] timeIntervalSince1970]] forKey:@"timestamp"];

        [pushHandler.pendingNotifications addObject:mutableUserInfo];

    }

}

/* STOP BLOCK */

前のコードは、基本的にPushNotificationクラスへの参照を作成します。このコードは、発生したイベントに応じて、値を設定または取得したり、パラメーターを指定して別のメソッドを呼び出したりします。Objective-Cについてよく知らなくても、現時点で難しそうだと感じても、心配しないでください。参考のために、「要件」セクションにダウンロードできるサンプルプロジェクトを用意しています。

最後に、この同じAppDelegate.mクラスのdidFinishLaunchingWithOptionsメソッドに、通知からのアプリケーションの起動を処理(し、受信したオブジェクトを後で取得するためにpendingNotificationsに追加)するコードを追加します。このブロックは、次のように、末尾のreturn YESの直前に追加します。

/* Handler when launching application from push notification */

// PushNotification - Handle launch from a push notification

NSDictionary* userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];

if(userInfo) {

    PushNotification *pushHandler = [self.viewController getCommandInstance:@"PushNotification"];

    NSMutableDictionary* mutableUserInfo = [userInfo mutableCopy];

    [mutableUserInfo setValue:@"1" forKey:@"applicationLaunchNotification"];

    [mutableUserInfo setValue:@"0" forKey:@"applicationStateActive"];

    [pushHandler.pendingNotifications addObject:mutableUserInfo];

}

/* end code block */

PushNotificationプラグイン–JavaScript APIs

前のネイティブObjective-Cハンドラーを含め、プロジェクトの設定が完了したので、Cordova PushNotification JavaScriptインターフェイスを使用して実際にコーディングを開始できます。ここでは、一部のインターフェイスの詳細と、コード内でインターフェイスを使用する例を示します。この記事では具体的に説明されているインターフェイス以外にも使用可能なインターフェイスがあります。

デバイスの登録

PhoneGap PushNotificationプラグインは、アプリケーションをAppleのPush Notification Serviceに登録し、プッシュ通知を受信するためのregisterDevice() API を提供します。この関数で、有効にする通知の種類(アラート、バッジ、サウンド)を正確に指定します。結果は、サーバー側でそのデバイスに通知を送信するために使用できる一意のデバイストークンになります。このトークンは変更される可能性があるので、Appleでは、アプリケーションを実行するたびに、デバイスでアプリケーションのプッシュ通知を登録することを推奨しています。

registerDevice()関数の使用例は、この記事の最初にある「要件」セクションからダウンロードできるサンプルプロジェクトに含まれています。そのコードを以下に示します。

var pushNotification = window.plugins.pushNotification;

pushNotification.registerDevice({alert:true, badge:true, sound:true}, function(status) {

    app.myLog.value+=JSON.stringify(['registerDevice status: ', status])+"\n";

    app.storeToken(status.deviceToken);

});

前のコードを記述して、アプリケーションを実行すると、図4に示されているアラートが表示されます。このアラートはユーザーにプッシュ通知を受信するかどうかを確認します(「OK」または「Don't Allow(許可しない)」をクリックすると設定が行われ、必要に応じてユーザーのデバイスの設定で変更できます)。

保留中の通知の取得

保留中の通知とは、アプリケーションがアクティブではない間に受信された通知です。アプリケーションが起動したときに、データを処理し、必要に応じて送信できるように、保留中の通知を取得します。API には、この目的のためにgetPendingNotifications()関数が用意されています。次のコード例を参照してください。

var pushNotification = window.plugins.pushNotification;

pushNotification.getPendingNotifications(function(notifications) {

    app.myLog.value+=JSON.stringify(['getPendingNotifications', notifications])+"\n";

    console.log(JSON.stringify(['getPendingNotifications', notifications]));

});

通知の状態の取得

このメソッドは、登録のチェックを実行し、有効になっている通知(アラート、サウンド、バッジ)を返します。

var pushNotification = window.plugins.pushNotification;

pushNotification.getRemoteNotificationStatus(function(status) {

    app.myLog.value+=JSON.stringify(['Registration check -getRemoteNotificationStatus', status])+"\n";

});

バッジ番号の設定

バッジ番号は、アプリケーションアイコンの右上隅に表示される数字によって、特定のアプリケーションの保留中の通知を示します。通知を処理するときに、アプリケーションアイコン上のバッジ番号を設定することが必要になる場合があります。例えば、受信したプッシュ通知から開いた場合、バッジ番号を減らしたり、クリアしたりすることが必要になります。次のように、バッジ番号を0に設定すると、バッジが削除またはクリアされます。

var pushNotification = window.plugins.pushNotification;

app.myLog.value+="Clear badge... \n";

pushNotification.setApplicationIconBadgeNumber(num);

Apple Push Notificationの詳細

通知ペイロードの最大サイズは256バイトです。この制限を超えると、通知は拒否されます。また、Appleのドキュメントによれば、通知の配信は「ベストエフォート型」であり、保証されていないため、重要なデータや機密データの送信には使用せず、新しいデータが利用可能であることを通知するだけにしてください。

通知ペイロードは、キーapsで識別される別のディクショナリを格納する必要があるJSONディクショナリオブジェクトです。次に、apsディクショナリは、表示するアラート、アプリケーションアイコンに設定するバッジ番号、通知時に再生するサウンドを指定する1つまたは複数のプロパティを格納します。カスタムペイロードを作成することもできますが、この記事では触れません。アラートオブジェクト自体には、表示するテキストの文字列や本文のキーを含むディクショナリオブジェクト、表示するカスタムアクションボタンのテキストや設定可能なカスタム起動画像を格納できます。このペイロードについて詳しくは、Apple Push Notification Serviceのドキュメントを参照してください。

バイト単位のシンプルなペイロードの例を以下に示します。

標準的なapsディクショナリオブジェクトには、alertbadgeおよびsoundの3つのプロパティが格納されます。例えば、次の出力を見てください。

applicationLaunchNotification = 0;

applicationStateActive = 0;

aps = {

alert = {

"action-loc-key" = Play;

body = "Your turn!";

"launch-image" = "mysplash.png";

};

badge = 5;

sound = "notification-beep.wav";

};

messageFrom = Holly;

timestamp = "1350416054.782263″;

カスタムサウンドや起動画像は、XCode内のプロジェクトのResourcesフォルダーに格納する必要があります。例えば、次に示す筆者の例では、サーバーからのプッシュ通知で参照するnotification-beep.wavmysplash.pngを指定しています。

サーバー側のコードの処理

次に示すコード例は、デバイストークンの登録を処理するサーバー側のソリューションを理解するために使用できます。このコードは、現在、筆者のサンプルアプリケーションから呼び出しているもので、Appleからデバイストークンを受信した後、デバイストークンを格納するサードパーティのサーバーと、どの時点で、どのように通信するのかを示しています。現在、トークンとメッセージが受信されたことを示す以外に、トークンを使った処理は何も行っていません。次のステップは、後で使用するために、ユーザーのデバイストークンとその他の情報をデータベースに格納することです。

var http = require('http');   

var apn = require('apn');

var qs = require('querystring');

 

var server = http.createServer(function (req, res) {

    if(req.method === "POST") {

        var fullBody="";

    

        req.on('data', function(chunk) 

        {

            fullBody += chunk;

            console.log("Full body " + fullBody);

        });

 

        req.on('end', function()

        {

            var data = qs.parse(fullBody);

            console.log("Token " +data.token);

            console.log("Message " + data.message);

            var myDevice = new apn.Device(data.token);

            // Now we need to store it! Add code to interface with a db

below...

 

            res.writeHead(200, {"Content-Type": "text/plain"});

            res.end("Thank you for registering\n");

            res.end();

        });

    }

}).listen(8888);

console.log("Server running at http://127.0.0.1:"+server.address().port);

筆者は、このスクリプトをNode.jsを使用してlocalhost上で実行しました。実際のデバイスからテストする場合(エミュレーターではプッシュ通知はサポートされていないため)、デバイスのWi-Fi設定で、手動HTTPプロキシがコンピューターのIPアドレスを指すように設定できます。使用するWi-Fiネットワークに接続し、設定の一番下までスクロールし、手動プロキシサーバーとポートを設定します。筆者の例を示します。

Argon(Node.js API)による通知の送信

次のシンプルなコード例では、argonオープンソースNode.js APIを使用して、ユーザーのデバイスにプッシュ通知を送信する方法を示しています。迅速にテストを行うためにデバイストークンをハードコーディングしていますが、最終的には、プッシュ通知の送信に使用するためにデータベースからデバイストークンを取得します。

var http = require('http');

var apn = require('apn');

var url = require('url');

 

var myPhone = "d2d8d2a652148a5cea89d827d23eee0d34447722a2e7defe72fe19d733697fb0";

var myiPad = "51798aaef34f439bbb57d6e668c5c5a780049dae840a0a3626453cd4922bc7ac";



var myDevice = new apn.Device(myPhone);



var note = new apn.Notification();

note.badge = 1;

note.sound = "notification-beep.wav";

note.alert = { "body" : "Your turn!", "action-loc-key" : "Play" , "launch-image" : "mysplash.png"};

note.payload = {'messageFrom': 'Holly'};



note.device = myDevice;



var callback = function(errorNum, notification){

    console.log('Error is: %s', errorNum);

    console.log("Note " + notification);

}

var options = {

    gateway: 'gateway.sandbox.push.apple.com', // this URL is different for Apple's Production Servers and changes when you go to production

    errorCallback: callback,

    cert: 'PushNotificationSampleCert.pem',                

    key:  'PushNotificationSampleKey.pem',                

    passphrase: 'myPassword',                

    port: 2195,                       

    enhanced: true,                  

    cacheLength: 100                  

}

var apnsConnection = new apn.Connection(options);

apnsConnection.sendNotification(note);

前のコードは、デバイスのSettings(設定)/Notifications(通知)/MyAppNameでAlerts(アラート)オプションが設定されている場合、デバイス上で次のような通知を表示します。

デバイスの設定で、アプリケーションの「Alert Style(通知のスタイル)」が「Banners(バナー)」に設定されている場合は、次のように表示されます。

重要:セットアップで作成された証明書と秘密鍵に対するPEMファイルを変更する必要があります(これらは、最終的には、SSLの証明書およびプロビジョニングセクションの命令で1つのPEMファイルに結合されます。最初にCERをPEMに変換した後、.p12をPEMに変換したことを思い出してください。これらがここで必要な2つのファイルです。argonのパラメーターについて詳しくは、argonのreadmeファイルを参照してください)。筆者の例では、PEMファイルはNode.jsコードと同じフォルダーにあります。ファイルにパスワードを設定していない場合は、passwordプロパティを省略できます。

次のステップ

ここで説明した内容がすべて含まれているリファレンスアプリケーションとして作成した、GitHub上のサンプルプロジェクトへのリンクを用意しました。このプロジェクトをそのまま実際に使用することはお勧めしません。自分のプロジェクトに対して設定された一意のApp IDとプロビジョニングファイルを取得する必要があるからです。独自のプロジェクトを一から作成し、サンプルは参考としてのみ使用することをお勧めします。このプロジェクトは、cordova-clientツールを使用して、最終的にはクロスプラットフォームになるように作成しました。これについては、筆者の別の記事「Cross-Platform PhoneGap (aka Cordova) Project Templates in a Jiffy!」(クロスプラットフォームのPhoneGap(旧Cordova)プロジェクトテンプレートの簡単設定)で説明していますが、現在は設定されているのはiOSのみです。したがって、iOS固有のコードについては、PGPushNotificationSample/platforms/iosパスを参照してください。すべてのJavaScript関数については、PGPushNotificationSample/platforms/ios/www/js/index.jsファイルに記載されています。