2012年8月22日 星期三

用 Node.js 完成 Apple Push Notification Service - node-apn

iOS 服務中 Push Notification 是一個很常被使用者的服務,使用者 Push Notification 可以主動通知使用者關於這個 App 的消息,使用者不用打開 App 就可以被通知有什麼消息。
要建立這項服務,開發者必需要有 Server Side Programming 的能力,而 Node.js 可以把學習曲線降到最底,讓我們來看看它神奇的地方。
首先安裝一個叫 node-apn 的 package。老方法用 npm
npm install -g node-apn
npm install -g apn

這樣就安裝好了。程式碼有多簡單呢?

var apns = require('apn');
var options = {
    cert: 'cert.pem',                 /* Certificate file path */
    key:  'key.pem',                  /* Key file path */
    gateway: 'gateway.sandbox.push.apple.com',/* gateway address */
    port: 2195,                       /* gateway port */
    errorCallback: errorHappened ,    /* Callback when error occurs function(err,notification) */
}; 
function errorHappened(err, notification){
    console.log("err " + err);
}
var apnsConnection = new apns.Connection(options);

var token = "xxxx;oiqwjf;oiwje;foiajw;f";
var myDevice = new apns.Device(token);
var note = new apns.Notification();
note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now.
note.badge = 1;
note.sound = "ping.aiff";
note.alert = "You have a new message";
note.payload = {'messageFrom': 'Caroline'};
note.device = myDevice;

apnsConnection.sendNotification(note);
// 名為 apnServer.js
就這樣不到 30 行,更詳細的文件可以參考這裡 node-apn,就可以從任意可以執行 Node.js 的電腦送訊息給 iOS 的 App。
其中有一些準備動作第一次要完成,比如 cert 和 key 的 file ,從 Apple iOS Portal Download 下來之後,要自行轉檔才可以得到,再來就是 token 某個 iOS App 會去 Apple 某一個 token ,代表著這個 App ,Push Notification 要和這個 Token 綁在一起,當這個 Push Notification 到達手機時,iOS 系統才會知道要送給那一個 App。
我們從上方一步一步來看,首先看到

var apns = require('apn');
var options = {
    cert: 'cert.pem',                 /* Certificate file path */
    key:  'key.pem',                  /* Key file path */
    gateway: 'gateway.sandbox.push.apple.com',/* gateway address */
    port: 2195,                       /* gateway port */
    errorCallback: errorHappened ,    /* Callback when error occurs function(err,notification) */
}; 
function errorHappened(err, notification){
    console.log("err " + err);
}

var apnsConnection = new apns.Connection(options);
一開始就使用 apn 這個 library,然後設定一些 options,比如 cert 和 key 檔的路徑檔名,再來就是測試用的 Apple 主機 -  gateway.sandbox.push.apple.com 和 port。要注意的是現在這個主機是測試用的,正式上線要改用 gateway.push.apple.com ,再來準備一個 errorCallBack 的 function,當這個連線有問題時,我可以從 errorHappend 得知錯誤情況。最後是新建一個 connection 然後指定到 apnsConnection。
解說到這先停一下,看要如何來產生 cert.pem 和 key.pem 這兩個 file.
進入 iOS Developer Portal 的時候先選到某一個 App IDs 的 Configure. 如下圖
接著要把 Enable Apple Push Notification Service 的
底下有兩個一個是 Development 另一個是 Production 。圖上是把 Development 點選 Configure 上傳自己的 Certification Request 然後得到 Developer Portal 的 Provisioning Profile 了。就會有一個下載的按鈕,上傳的步驟就和一開始要得到 Developer Provisioning Profile 一樣,就不多說了,下載之後會在 Keychain Access 出現以下畫面。
一個一個產生,在 Certificates 上面(也就是紅色外框圖示)按右鍵,選 export,如下圖
File Format 選 Certificate,然後命名為 cert.cer,存到 apnServer.js file 的路徑。如下
同樣方法,按右鍵 export <key> 如下
這次的 format 選 .p12 如下圖,命名為 key.p12,和剛剛的 cert.cer 同一個資料夾
用 Terminal 在這兩個檔案的目錄下,依序輸入這兩行指令。
$ openssl x509 -in cert.cer -inform DER -outform PEM -out cert.pem
$ openssl pkcs12 -in key.p12 -out key.pem -nodes
這樣一個 key.pem 和 cert.pem 都準備好了。回到 apnServer.js 看看

var token = "xxxx;oiqwjf;oiwje;foiajw;f";
var myDevice = new apns.Device(token);
var note = new apns.Notification();
note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now.
note.badge = 1;
note.sound = "ping.aiff";
note.alert = "You have a new message";
note.payload = {'messageFrom': 'Caroline'};
note.device = myDevice;

apnsConnection.sendNotification(note);
之前建立了一個 connection 物件,接著就看到一個奇怪的 token 變數,後面的字串是來亂的,這個 token 就是要從 iOS App 去和 Apple 連絡,得到的字串,等一下會看到 iOS 程式的寫法。接著利用這個 token 產生一個要傳送 notification 的 myDevice。跟著利用 apns 產生 notification,設定 expiration time 還有 badge, sound,alert 字樣,json 格式的 payload 可以傳給 App。最後把 note 和 device 綁定,然後就 sendNotification(note)。App 就會收到 notification 了。在這樣要注意的是,notification 並不一定會被送到,這是 Apple 文件寫的很明白的地方,所以重要的資料還是要 App 主動去和 Server 要,千萬不要依賴 Push Notification 傳資料。
最後我們來看一下 iOS App 怎麼取得專屬於自己的 token?
在任何的 App Source 的 AppDelegate.m 裡如下新增。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
    [application registerForRemoteNotificationTypes:
     UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound]; 
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
    NSString * tempToken = [deviceToken description];
    self.token = [tempToken stringByReplacingOccurrencesOfString:@"<" withString:@""];
    self.token = [self.token stringByReplacingOccurrencesOfString:@">" withString:@""];
    self.token = [[self.token componentsSeparatedByString:@" "] componentsJoinedByString:@"" ];
    NSLog(@"got string token %@", self.token);
}
這樣一來就會在 console 看到一串的token,屆時再把 self.token 這個傳到 Server 去接,這個部分就留給讀者們去發揮了。咱們下次見。


2 則留言:

  1. 作者已經移除這則留言。

    回覆刪除
  2. 請問這個作法跟用php,java,c#透過gateway.sandbox.push.apple.com 發notify有何不同呢

    回覆刪除