プログラミング

【Swift】VoIP Push通知をAmazon SNSから受け取る

swift-eyecatch

VoIP Push通信とは、普通のPush通知とは異なり、電話Call専用のPush通知です。

VoIPのPush通知を受信するには、iOSではPushKitを使用する必要があります。

また以前は、VoIP通信でも通常のPush通知のように使用できたのですが、現在はAppleの使用により、iOS13からはVoIP通信を受け取ったらCallKitを呼ぶ必要があり、そうしないと、VoIP通信が送られなくなってしまいます。

つまり、VoIPで電話を受信する機能を実装するには、PushKitとCallKitの両方を実装することが必須になります。

CallKitは着信のUIを開発するアプリで使用するものになります。例えばLINEで着信を受けたときなどは、通常の電話番号での電話着信と同じUIが表示されます。これを実現するためには、VoIP Push通知を受信 -> CallKitでUI表示と実装する必要があります。

なお、CallKitはUI表示のみなので、実際のビデオ電話などの機能を実装するには、他のモジュールと組み合わせて実装する必要があります。この辺はまた次回の記事にします。

この記事では、VoIP Push通信をPushKitを使用して受信する部分の実装について触れます。VoIP Pushを送る側は、AmazonのSNSを使用していますが、参考にaws cliによる方法と、Pusherというアプリを用いる方法も記載しています。

VoIP Push通知の実装

VoIPでPush通知を受信するには、以下が必須となります。

  • VoIP専用の証明書の作成
  • XcodeでPushKitの実装(受信側)
  • 何かしらのPushを送る側の実装(送信側)

それぞれ順に手順を記載します。

CSRファイルの作成方法

VoIPの証明書を作成するために、後ほど必要になるCSRファイルを先に作成しておきます。(順番が前後しても問題はありません。)

Macの「キーチェーンアクセス」アプリを開き、「証明書アシスタント」->「認証局に証明書を要求」を選択します。

表示されるポップアップでは、以下の5項目の入力が求められます。

  • ユーザのメールアドレス
  • 通称
  • CAのメールアドレス
  • 要求の処理
  • 鍵ペア情報を指定

ユーザのメールアドレス、通称、CAのメールアドレスはデフォルトのまま、「要求の処理」は「ディスクに保存」を選択、そして、「鍵ペア情報を指定」にチェックし、「続ける」をクリックします。

これでCertificate Signing Request (CSR)を作成出来ますので、デスクトップなど任意の場所に保存しておきます。このファイルは後ほど必要になります。

VoIP専用の証明書の作成方法

VoIP専用の証明書を作成するには、Apple Developer Accountが必要になります。SafariでApple Developer Accountにログインし、メニュー左側の「Certificates, ID & Profiles」を選択します。

Apple Developer Accountを使用する際には、Safariが推奨されています。

次にVoIP専用の証明書を作成するために、まずはApp IDを作成します。専用のApp IDを作成しないと先に進めません。

App IDの作成方法

左側の「Identifier」を選択し、「+」ボタンをクリックします。

「App IDs」を選択し、「Continue」をクリックします。次に「App」もしくは「App Clip」が選択出来ますが、「App」を選択します。

表示された画面で、「Description」「Bundle ID」「Capabilities」を入力していきます。

「Description」には、リリースする時のアプリケーション名などを入力し、「Bundle ID」には、よく用いられる「com.domain.appname」の表記で入力します。

ここで入力した「Bundle ID」は後ほどXcodeで入力することになるので、忘れないようにメモなどをとっておきます。

最後に「Capabilities」には、Push Notificationsにチェックを入れます。そして右上の「Continue」でApp IDの作成完了です。

Certificatesの作成方法

次に、VoIP専用の証明書(Certificates)を作成していきます。

左側のメニューから「Certificates」を選択し、「+」ボタンをクリックします。

「Services」のセクションに、「VoIP Services Certificate」がありますので、こちらにチェックをして、右上の「Continue」をクリックします。

そうすると、App IDを選択する画面に遷移するので、先程作成したApp IDを選択します。

App IDを作成していないと、これ以上進めませんので、先にApp IDを作成してください。

次にCSRファイルをアップロードする必要がありますので、先程作成した、CSRファイルをアップロードします。

これでVoIPの証明書が作成できたので、ダウンロードしてローカルに保存し、ダブルクリックして、キーチェーンに保存します。

キーチェーンに保存されたことを確認して完了です。

P12ファイルの作成方法

次に作成したVoIPファイルをサーバーにアップロードする必要があります。

今回はAmazon SNSを使用するので、P12ファイルが必要になります。

P12ファイルは、ダウンロードしたVoIPの証明書を、キーチェーンから右クリックで書き出すことで作成することが出来ます。

形式が.p12になっていることを確認し、「保存」をクリックします。パスワードも合わせて入力します。

パスワードを忘れてしまうと、再度P12ファイルを書き出す必要があります。

これでサーバーへアップロードするファイルの作成が完了です。

次は、AWSでAmazon SNSを設定していきます。

AWS SNSの設定方法

AWS consoleにログインし、SNSにアクセスします。

左側メニューの「プッシュ通知」を選択し、「プラットフォームアプリケーション」の作成をクリックします。

表示される画面でいくつかの設定と証明書のアップロードを行います。

「アプリケーション名」には、任意の文字列を入力します。

「プッシュ通知プラットフォーム」では、「Apple iOS/VoIP/Mac」を選択します。

今回は開発環境でのテストなので、「サンドボックスでの開発に使用されます」にチェックをいれます。

「プッシュ証明書タイプ」には、「VoIPプッシュ証明書」を選択します。

そして、証明書をアップロードし、パスワードを入力して「認証情報をファイルから読み込み」をクリックし、証明書を読み込ませます。そして最後に右下の「プラットフォームアプリケーションの作成」をクリックします。

次に、作成したプラットフォームアプリケーションに端末を特定するエンドポイントを入力する必要がありますが、エンドポイントを確認するには、XcodeでのPushKitの実装が先に必要になります。後ほどAWSに戻ってきます。

XcodeでPushKitを最低限実装する方法

TARGETSの設定

VoIPのPush通知を受信するためには、いくつか設定する必要があります。

Xcodeで「TARGET」->「Signing & Capabilities」にて、証明書を作成した時に指定した「Bundle ID」を入力します。

次に「+ Capabilities」をクリックして、「Background Modes」と「Push Notification」を追加します。

「Background Modes」では、最低限「Voice over IP」と「Background fetch」にチェックを入れます。「Background fetch」にチェックを入れることで、アプリが起動していない状態でもVoIPを受信することが出来るようになります。

Info.plistの確認

先程TARGETSで設定した内容が反映されていることを確認します。Required background modesの項目に以下の項目が表示されていない場合は、手動で追加してください。

AppDelegateの編集

次にいよいよPushKitの実装です。といっても最低限の実装のみの記載になりますが。

AppDelegateで、PushKitimportし各種設定するコードを実装していきます。

import PushKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        setupPushKit()
        return true
    }
}

extension AppDelegate {
    func setupPushKit() {
        print("test: setupPushKit()")
        let voipRegistry: PKPushRegistry = PKPushRegistry(queue: .main)
        voipRegistry.delegate = self
        voipRegistry.desiredPushTypes = [.voIP]
    }
}

extension AppDelegate: PKPushRegistryDelegate {
    func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
        print("test: didUpdate pushCredentials")
        let pkid = pushCredentials.token.map { String(format: "%02.2hhx", $0) }.joined()
        print("your device token: \(pkid)")
    }

    func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
        print("test: didInvalidatePushTokenFor")
    }

    func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
        print("test: didReceiveIncomingPushWith")
        //incomingCall()
    }
}

AppDelegateを実装し、アプリを起動すると、Xcodeのデバッグ出力欄にdevice tokenが表示されます。

表示されたdevice tokenをAWS SNSのコンソールでエンドポイントとして設定します。

なお、VoIP Push通知を端末が受信すると、func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType)の関数が呼ばれるので、ここで確認をしていきます。

実際には、これが呼ばれたタイミングでCallKitと連携して、CallKitのUIを表示される流れになります。(詳細はまた後日記載しようと思います。)

AWS SNSでエンドポイントを登録する

AWSのコンソールに戻り、先程作成したSNSのプラットフォームアプリケーションを開き「アプリケーションエンドポイントの作成」をクリックします。

表示される画面で、先程Xcodeで取得した、device tokenを入力します。

ユーザーデータはオプションなので何も入力しなくて大丈夫です。

作成されたエンドポイントを選択し、「メッセージを発行」をクリックしてコンソールからVoIP送信を試してみます。

Xcodeのデバッグ出力欄に、didReceiveIncomingPushWithと表示されれば成功です。

CallKitを実装していない段階で、VoIPのPush通知を受信しすぎると、VoIPのPush通信がAppleによりBanされてしまいますので、疎通が確認できたら、すぐにCallKitの実装に映るのが吉です。

AWS SNSを別の方法で叩く

今回は、AWSのコンソールから1件手動でVoIP Pushを送信しましたが、実際に運用していく段階では手動では行わないと思います。

いくつか方法はあるのですが、今回はVoIP Push通知の疎通の確認の為に試した方法を2つ紹介します。

AWS Cli -> APNs -> Mobile への VoIP Push通知

コンソール以外にも、AWSのCliコマンドでSNSを送信することが出来ます。

VoIPのPush通知の際にはいくつか注意点があって、「間違ってしまうと送信されたように見えるが実際には届かない」という自体に陥ります。Appleの公式ドキュメントによると、送信内容が間違っていると遅れが出たりこっそりと破棄されてしまったりします。(辛い)

以下は疎通の確認が出来たコマンド例です。aws cliをインストールし設定が完了していることが前提となります。

aws sns publish \
--profile your_aws_user \
--target-arn arn:aws:sns:us-east-1:xxxxxxxxxxxx:endpoint/APNS_VOIP_SANDBOX/platform_name/your_endpoint \
--message-attributes '{ "AWS.SNS.MOBILE.APNS.PUSH_TYPE": {"DataType":"String","StringValue":"voip"}}' \
--message '{"APNS_VOIP_SANDBOX":"{\"aps\": {\"foo\":\"bar\"}}"}' \
--message-structure json

--profileで使用するcliのprofileを指定しています。何もしていないと、regionを指定しろと怒られたりします。
--target-arnには、作成したエンドポイントの「ARN」を設定します。これはコンソール上で確認することが可能です。

※なお、APNS_VOIP_SANDOXの表記は、開発環境用となります。本番環境の場合は、APNS_VOIPとなり、SANDBOXがなくなります。

重要なのは、--message-attributesです。(全て重要ですが)
ここには、voipを使用するという指定をします。間違って入力すると、VoIP Push通知は届きません。

Pusher -> APNs -> Mobile への VoIP Push通知

他にも「Pusher」というミニアプリを使う方法もあります。こちらは、AWSは関係ないですが、証明書さえあれば、ローカルPCからAPNsにVoIPのプッシュ通知送信依頼を送ることが出来ます。操作性もシンプルでわかりやすいです。
https://github.com/noodlewerk/NWPusher

こちらから最新のreleaseをダウンロードすることも可能です。
Release 0.7.5 · noodlewerk/NWPusher · GitHub

zipファイルをダウンロードしてダブルクリックすると、`.app`ファイルがあるので、それをダブルクリックすればアプリが立ち上がります。

ダウンロードすると開発者の顔(と思われる)のアイコンのアプリが使えるようになります。初見は少し怖いです。がちゃんと動きます。

作成した証明書と、device tokenを入力して、Pushすると、直接APNsに飛ばしてくれます。端末にVoIPのPush通知が飛んでくれば成功です。

以上になります。少し長くなりましたが、ざっとVoIPを実装する手順を記載しました。次は、VoIPを受信したらCllKitと連携する部分を記載しようと思います。