ナカザンドットネット

Android Developer's memo

iOS 13 プッシュ通知ヘッダー必須問題(未解決)とNSDataのdescriptionの話

いろいろ触ってみたんですが、なんだか状況がよく見えないので会社ブログやQiitaに書くわけにもいかず、ひとまず個人ブログで供養しますみたいなやつです。

Motivation

iOS 13(とwatchOS 6)以降のデバイスに対してプッシュ通知を送る場合に、追加のヘッダーが必須になるという情報が、ドキュメントに追記されていました(2019年8月20日現在)。

Header field Description
apns-push-type (Required when delivering notifications to devices running iOS 13 and later, or watchOS 6 and later. Ignored on earlier system versions.) The type of the notification. The value of this header is alert or background. Specify alert when the delivery of your notification displays an alert, plays a sound, or badges your app's icon. Specify background for silent notifications that do not interact with the user.

The value of this header must accurately reflect the contents of your notification's payload. If there is a mismatch, or if the header is missing on required systems, APNs may delay the delivery of the notification or drop it altogether.

For information about configuring notification alerts, see Generating a Remote Notification. For more information about sending background notifications, see Pushing Background Updates to Your App.

Sending Notification Requests to APNs | Apple Developer Documentation

要約すると

  • apns-push-type ヘッダーに alertbackground を指定してください
    • alert を指定するのは、ユーザーに通知の存在を見せたい場合です
    • background を指定するのは、ユーザーに何も気づかせない(バックグラウンドプロセスを起動するのみに留める)場合です

これまで受け取り側(iOSデバイス側)で振り分けていたものを、サーバー側から制御する意図があるようです。

apns-push-typeを付与しないとどうなるのか

APNsに対して行ったプッシュ通知依頼のPOSTリクエストに、 400 InvalidPushType というエラーレスポンスが返るそうです(Table 5 Response error stringsより)。

Amazon SNSとか大丈夫?

同一ユーザーの複数デバイス宛や、あるセグメントのユーザーなど、ある程度のまとまったプッシュ通知を、Android/iOSの両方に送りたい場合などに、Amazon SNSのMobile Pushといった、プッシュ通知のラッパー的なサービスを使うことがあると思います。

これらのサービスは、 apns-push-type ヘッダーを付与してくれるのでしょうか。また、付与しなかった場合のエラーハンドリングはどうすればよいでしょうか。

最悪のケースとしてiOS 13が正式リリースされた直後から、ユーザーにプッシュ通知が届かなくなる恐れもあると思われたため、調べてみることにしました。

apns-push-type問題の結論

2019年8月現在のSANDBOX環境に限っていえば、Amazon SNS由来のプッシュ通知はiOS 13デバイスに届きました。

エラーが出ることを確認してAmazonに直談判するつもりだったので、ちょっと拍子抜けです。

Amazonがこっそり apns-push-type を付与する改修を済ませていたのか、AppleがまだAPNsサーバーにドキュメントの内容を適用していないのか……後者だったら怖すぎる……

deviceTokenが正しく取れない罠を踏んだ

ここからは余談です。

Cordova iOS製のアプリをiOS 13デバイスに入れて検証していたのですが、序盤にAPNsから 400 BadDeviceToken のエラーをもらってしまいました。

Cordovaでプッシュ通知を扱う場合はphonegap-plugin-pushを使います。デバイス登録を行った際の deviceToken を、16進数文字列に変換してから渡してくれるので、ちょっと便利です。

さて、 BadDeviceToken というからには deviceToken にミスがあったのだろうとログを仕込んでみると、次の文字列が出てきました。

"{length=32,bytes=0x3dd89c70dabd0be290cc2f8e215132e0...75a03b3cbf9bbe0a}"

これをBase64エンコードしたものを使ってAPNsにリクエストした結果として、 BadDeviceToken が出ていたようです。さもありなん。

さて、 bytes の中身は16進数っぽいですが、謎の省略がされています。正規表現で抜き出すということもできなさそうです。Objective-C側の挙動が変わったようで、JavaScript側からはどうにもできなさそうです。

実装上の意図されていた挙動

phonegap-plugin-pushのコードを読んでみると、NSData型の deviceToken から description で文字列を取り出した後、加工することで16進数文字列を作っているようでした。

NSString *token = [[[[deviceToken description] stringByReplacingOccurrencesOfString:@"<"withString:@""]
                   stringByReplacingOccurrencesOfString:@">" withString:@""]
                   stringByReplacingOccurrencesOfString: @" " withString: @""];

https://github.com/phonegap/phonegap-plugin-push/blob/110e3c2/src/ios/PushPlugin.m#L365

上記の処理では、[deviceToken description] の結果が次のような文字列になることを期待しています。

<124686a5 556a72ca d808f572 00c323b9 3eff9285 92445590 3225757d b83997ba>

ほぼバイナリの16進数表記ですね。これを加工することで、NSString *token には 124686a5556a72cad808f57200c323b93eff9285924455903225757db83997ba という文字列が格納されていたわけです。

これがなぜ {length=32,bytes=...} になってしまったのでしょうか。

NSData#descriptionの挙動が変わった?

Appleのフォーラムを調べてみると、似たような話題が見つかりました。

プッシュ通知や deviceToken がどうのこうのというよりは、 description の挙動そのものが変わってしまったような印象も受けますが、どうなんでしょう………

phonegap-plugin-pushにPR投げました

ひとまず目の前の問題として、phonegap-plugin-pushを何とかしないといけないので、iOS 13以降では description を使わずに deviceToken の16進数文字列を生成できるようにしたPRを作成しました。

github.com

フォーラムでFBの実装がいいよ!という書き込みがあったので、ほぼコピペです。

Objective-CでOSSにPRを投げる実績を解除したぞ!!!🎉 (まだマージされたわけではない)

まとめ

iOS 13でプッシュ通知が動かなくなりそうな罠を複数見つけてしまって、気が気でないですね……

どうか穏便に正式リリースの日を迎えたい……