Upgrade to Pro — share decks privately, control downloads, hide ads and more …

APNsからLive Activityを開始する話

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →

APNsからLive Activityを開始する話

DroidKaigi & iOSDC After Talks Night 2026
https://yumemi.connpass.com/event/402647/

Avatar for Ryotaro Onoue

Ryotaro Onoue

October 02, 2026

More Decks by Ryotaro Onoue

Other Decks in Programming

Transcript

  1. 0. 自己紹介 - 尾上 遼太朗 (おのうえ りょうたろう) - X: @YumNumm

    (もぐもぐ) - 2004.09.17生まれ (22歳) - 神奈川県在住 経歴 - [2023/04] 株式会社 ゆめみ 新卒入社 - [2025/12] アクセンチュア株式会社 仕事ではFlutterエンジニア 好き✌ - 自宅サーバ - バイク(CBR250RR) - 深夜早朝駆動開発 - カメラ(SONY α7C Ⅱ) 2
  2. 0. 自己紹介 - 自宅サーバやってます〜 - ラックサーバが4台 - 計算資源 - RX2530

    M4: 40C80T, 128GB - RX2540 M4: 40C80T, 16GB - DL160 gen9: 8C16T, 32GB - DL360p gen8: 20C40T, 192GB - 利用目的 - 個人開発(後述) - ↑の監視, 常時動かしたいサービス系 - Talos LinuxでKubernetesクラスタ - 3台運用, 1台は寝ている - ネットワーク機器はUbiquiti社のUniFiシリ ーズで統一 - UDM SE, USW Aggregation, USW Pro HD 24 PoE, U7 Pro, UNAS Pro .etc 3
  3. 1. 個人開発しているアプリの紹介 • アプリ • Flutter製 (iOS, Android) • サーバ

    • APIはTS, 通知配信はGolang, データ処理に一部Rust • インフラ • 自宅サーバのkubernetes • (Oracle Cloudで一部冗長化) 7
  4. 1. 個人開発しているアプリの紹介 • アプリ • Flutter製 (iOS, Android) • サーバ

    • APIはTS, 通知配信はGolang, データ処理に一部Rust • インフラ • 自宅サーバのkubernetes • (Oracle Cloudで一部冗長化) • 旧バージョンは、App Store/Google Playで公開中 • 新しいバージョン(v3; Live Activity対応版)は、現在審査中 • 週末にリリースできるんじゃないかな〜 • MAU 2.5万ほど • 大赤字 8
  5. 2. 緊急地震速報とは? ・震源付近の地震計で揺れを検知 -> 地震の規模や位置を推定 -> 予想される震度階級や到達時間を計算 (他の手法もあります; ex. PLUM法,

    レベル法) ↑気象庁 緊急地震速報とは https://www.jma.go.jp/jma/kishou/know/jishin/eew/shikumi/shousai.html 10 より引用
  6. 2. 緊急地震速報とは? ・震源付近の地震計で揺れを検知 -> 地震の規模や位置を推定 -> 予想される震度階級や到達時間を計算 (他の手法もあります; ex. PLUM法,

    レベル法) ・緊急地震速報には2種類ある ・警報: (主に)震度5弱以上が予想された時に、震度4以上の地域を 発表 ・予報: (主に)M3.5以上 or 予想震度3以上の場合 ↑気象庁 緊急地震速報(警報)及び(予報)について https://www.jma.go.jp/jma/kishou/know/jishin/eew/shikumi/shousai.html より引用 [MEMO] 緊急地震速報(警報)の発表条件: 地震波が2点以上の地震観測点で観測され、最大震度が5弱以上または最大長周期地震動階級が3以上と予想された場合に発表 緊急地震速報(予報)の発表条件: いずれかの地震観測点において、P波またはS波の振幅が100gal(cm/s)以上になった場合 / 地震計で観測された地震波を解析した結果、震源・マグニチュード・各地の予測震度、予測長周期地震動階級が求まり、そのマグニチュードが3.5以上 または 最大震度が3以上 もしくは 最大長周期地震動階級が1以上と予想された場合 11
  7. 2. 緊急地震速報とは? ・発表内容 ・震源付近の地震計で揺れを検知 -> 地震の規模や位置を推定 - ・強い揺れが予想される地域 と 震度4が予想される地域の名

    > 予想される震度階級や到達時間を計算 前 (全国を約200地域に分割したもの) (他の手法もあります; ex. PLUM法, レベル法) ・震源要素 (規模を示すマグニチュード, 緯度経度, ・緊急地震速報には2種類ある 発生時刻, 震央地名) ・警報: (主に)震度5弱以上が予想された時に、震度4以上の地域を 発表 ・予報: (主に)M3.5以上 or 予想震度3以上の場合 ↑気象庁 緊急地震速報(警報)及び(予報)について https://www.jma.go.jp/jma/kishou/know/jishin/eew/shikumi/shousai.html より引用 [MEMO] 緊急地震速報予報の発表条件: いずれかの地震観測点において、P波またはS波の振幅が100gal(cm/s)以上になった場合 / 地震計で観測された地震波を解析した結果、震源・マグニチュード・各地の予測震度、予測長周期地震動階級が求まり、そのマグニチュードが3.5以上 または 最大震度が3以上 もしくは 最大長周期地震動階級が1以上と予想された場合 12
  8. ETWS (地震・津波警報システム 2. 緊急地震速報とは? ・震源付近の地震計で揺れを検知 -> 地震の規模や位置を推定 > 予想される震度階級や到達時間を計算 (他の手法もあります;

    ex. PLUM法, レベル法) ・緊急地震速報には2種類ある ・警報: (主に)震度5弱以上が予想された時に、震度4以上の地域を 発表 能登半島地震で鳴り響いた「緊急地震速報」 受信できない スマホはあったのか - ITmediaより ・予報: (主に)M3.5以上 or 予想震度3以上の場合 防災無線 TV ↑気象庁 緊急地震速報(警報)及び(予報)について https://www.jma.go.jp/jma/kishou/know/jishin/eew/shikumi/shousai.html より引用 [MEMO] 緊急地震速報予報の発表条件: いずれかの地震観測点において、P波またはS波の振幅が100gal(cm/s)以上になった場合 / 茨城県矢坂市HPより ゆれの前に流れる「緊急地震速報」- NHK for School 地震計で観測された地震波を解析した結果、震源・マグニチュード・各地の予測震度、予測長周期地震動階級が求まり、そのマグニチュードが3.5以上 または 最大震度が3以上 もしくは 最大長周期地震動階級が1以上と予想された場合 13
  9. 2. 緊急地震速報とは? ・震源付近の地震計で揺れを検知 -> 地震の規模や位置を推定 > 予想される震度階級や到達時間を計算 (他の手法もあります; ex. PLUM法,

    レベル法) ・緊急地震速報には2種類ある ・警報: (主に)震度5弱以上が予想された時に、震度4以上の地域を 発表 ・予報: (主に)M3.5以上 or 予想震度3以上の場合 ← ?? ↑気象庁 緊急地震速報(警報)及び(予報)について https://www.jma.go.jp/jma/kishou/know/jishin/eew/shikumi/shousai.html より引用 [MEMO] 緊急地震速報予報の発表条件: いずれかの地震観測点において、P波またはS波の振幅が100gal(cm/s)以上になった場合 / 地震計で観測された地震波を解析した結果、震源・マグニチュード・各地の予測震度、予測長周期地震動階級が求まり、そのマグニチュードが3.5以上 または 最大震度が3以上 もしくは 最大長周期地震動階級が1以上と予想された場合 14
  10. 2. 緊急地震速報とは? ・発表内容 ・強い揺れが予想される地域 と 震度4が予想される地域の名前 (全国を約 200地域に分割したもの) ・震源付近の地震計で揺れを検知 ->

    地震の規模や位置を推定 ・強い揺れが予想される地域 と 震度4が予想される地域それぞれの予想 > 予想される震度階級や到達時間を計算 (他の手法もあります; ex. PLUM法, レベル法) ・緊急地震速報には2種類ある 震度, 到達予想時刻 ・予想最大震度 ・震源要素 (規模を示すマグニチュード, 緯度経度, ・警報: (主に)震度5弱以上が予想された時に、震度4以上の地域を 発生時刻, 震央地名) 発表 ・予報: (主に)M3.5以上 or 予想震度3以上の場合 ・緊急地震速報(警報)が発表された時には、緊急地震速報(予報)も同時に発 表される ↑気象庁 緊急地震速報(警報)及び(予報)について https://www.jma.go.jp/jma/kishou/know/jishin/eew/shikumi/shousai.html より引用 [MEMO] 緊急地震速報予報の発表条件: いずれかの地震観測点において、P波またはS波の振幅が100gal(cm/s)以上になった場合 / 地震計で観測された地震波を解析した結果、震源・マグニチュード・各地の予測震度、予測長周期地震動階級が求まり、そのマグニチュードが3.5以上 または 最大震度が3以上 もしくは 最大長周期地震動階級が1以上と予想された場合 15
  11. 2. 緊急地震速報とは? ・震源付近の地震計で揺れを検知 -> 地震の規模や位置を推定 > 予想される震度階級や到達時間を計算 (他の手法もあります; ex. PLUM法,

    レベル法) NERV防災アプリ HPより ・緊急地震速報には2種類ある ・警報: (主に)震度5弱以上が予想された時に、震度4以上の地域を 発表 ・予報: (主に)M3.5以上 or 予想震度3以上の場合 ↑気象庁 緊急地震速報(警報)及び(予報)について https://www.jma.go.jp/jma/kishou/know/jishin/eew/shikumi/shousai.html より引用 [MEMO] 緊急地震速報予報の発表条件: いずれかの地震観測点において、P波またはS波の振幅が100gal(cm/s)以上になった場合 / Yahoo!防災速報アプリ HPより 地震計で観測された地震波を解析した結果、震源・マグニチュード・各地の予測震度、予測長周期地震動階級が求まり、そのマグニチュードが3.5以上 または 最大震度が3以上 もしくは 最大長周期地震動階級が1以上と予想された場合 16 業種別の具体的な事例 - 気象庁より
  12. 2. 緊急地震速報とは? ・発表内容 ・強い揺れが予想される地域 と 震度4が予想される地域の名前 (全国を 約200地域に分割したもの) ・震源付近の地震計で揺れを検知 ->

    地震の規模や位置を推定 ・強い揺れが予想される地域 と 震度4が予想される地域それぞれの予想 > 予想される震度階級や到達時間を計算 震度, 到達予想時刻 (他の手法もあります; ex. PLUM法, レベル法) ・予想最大震度 ・緊急地震速報には2種類ある ・震源要素 (規模を示すマグニチュード, 緯度経度, ・警報: (主に)震度5弱以上が予想された時に、震度4以上の地域を 発生時刻, 震央地名) 発表 ・予報: (主に)M3.5以上 or 予想震度3以上の場合 -> 地域の予想震度・到達予想時刻も知りたい! というパワーユーザ向け -> 震度4未満の地震情報も知りたい人向け ↑気象庁 緊急地震速報(警報)及び(予報)について https://www.jma.go.jp/jma/kishou/know/jishin/eew/shikumi/shousai.html より引用 [MEMO] 緊急地震速報予報の発表条件: いずれかの地震観測点において、P波またはS波の振幅が100gal(cm/s)以上になった場合 / 地震計で観測された地震波を解析した結果、震源・マグニチュード・各地の予測震度、予測長周期地震動階級が求まり、そのマグニチュードが3.5以上 または 最大震度が3以上 もしくは 最大長周期地震動階級が1以上と予想された場合 17
  13. 2. 緊急地震速報とは? ・震源付近の地震計で揺れを検知 -> 地震の規模や位置を推定 > 予想される震度階級や到達時間を計算 (他の手法もあります; ex. PLUM法,

    レベル法) NERV防災アプリ HPより ・緊急地震速報には2種類ある ・警報: (主に)震度5弱以上が予想された時に、震度4以上の地域を 発表 ・予報: (主に)M3.5以上 or 予想震度3以上の場合 ← ?? ↑気象庁 緊急地震速報(警報)及び(予報)について https://www.jma.go.jp/jma/kishou/know/jishin/eew/shikumi/shousai.html より引用 [MEMO] 緊急地震速報予報の発表条件: いずれかの地震観測点において、P波またはS波の振幅が100gal(cm/s)以上になった場合 / Yahoo!防災速報アプリ HPより 地震計で観測された地震波を解析した結果、震源・マグニチュード・各地の予測震度、予測長周期地震動階級が求まり、そのマグニチュードが3.5以上 または 最大震度が3以上 もしくは 最大長周期地震動階級が1以上と予想された場合 18 業種別の具体的な事例 - 気象庁より
  14. 2. 緊急地震速報とは? ・震源付近の地震計で揺れを検知 -> 地震の規模や位置を推定 > 予想される震度階級や到達時間を計算 (他の手法もあります; ex. PLUM法,

    レベル法) ・緊急地震速報には2種類ある ・警報: (主に)震度5弱以上が予想された時に、震度4以上の地域を 発表 ・予報: (主に)M3.5以上 or 予想震度3以上の場合 令和5年4月から令和6年3月まで に発表した緊急地震速報の検証 緊急地震速報(警報)の発表状況 - 気象庁 地震火山部 より ↑気象庁 緊急地震速報(警報)及び(予報)について https://www.jma.go.jp/jma/kishou/know/jishin/eew/shikumi/shousai.html より引用 2026/01/01~10/01の発表状況 緊急地震速報(予報): 865回 [MEMO] 緊急地震速報予報の発表条件: いずれかの地震観測点において、P波またはS波の振幅が100gal(cm/s)以上になった場合 / 緊急地震速報(警報): 21回 地震計で観測された地震波を解析した結果、震源・マグニチュード・各地の予測震度、予測長周期地震動階級が求まり、そのマグニチュードが3.5以上 または 最大震度が3以上 もしくは 最大長周期地震動階級が1以上と予想された場合 19
  15. 3. Live Activity • iOS 16.1(2022)でLive Activityが導入された • ActivityKitフレームワークを利用して、ライブアップデートを共有する ActivityKit

    - Apple Developer より ライブアクティビティを開始できる • iOSのロック画面+Dynamic Island, watchOSのスマートスタック, macOSのメニューバー, CarPlayのホーム画面に 表示 • APNs経由でLive Activityを更新できるようになった(iOS 17.1+) • Broadcast push notificationを利用して一斉に表示を書き換えられるようになった (iOS 18+ APNs: Apple Push Notifications Service, サーバからのリモート通知とかを担うサーバのこと 21
  16. 3. Live Activity • iOS 16.1(2022)でLive Activityが導入された • ActivityKitフレームワークを利用して、ライブアップデートを共有する ActivityKit

    - Apple Developer より ライブアクティビティを開始できる • iOSのロック画面+Dynamic Island, watchOSのスマートスタック, macOSのメニューバー, CarPlayのホーム画面に 表示 • APNs経由でLive Activityを更新できるようになった(iOS 17.1+) • Broadcast push notificationを利用して一斉に表示を書き換えられるようになった (iOS 18+) APNs: Apple Push Notifications Service, サーバからのリモート通知とかを担うサーバのこと 22
  17. 3. Live Activity • iOS 16.1(2022)でLive Activityが導入された • ActivityKitフレームワークを利用して、ライブアップデートを共有する ライブアクティビティを開始できる,

    最大8h生きる ActivityKit - Apple Developer より • iOSのロック画面+Dynamic Island, watchOSのスマートスタック, macOSのメニューバー, CarPlayのホーム画面に 表示 • APNs経由でLive Activityを更新できるようになった(iOS 17.1+) • Broadcast push notificationを利用して一斉に表示を書き換えられるようになった (iOS 18+) -> APNsを通じて、サーバからLive Activityを開始/更新/終了できる! -> 緊急地震速報が発表された時に、Live Activityで情報を表示できたら便利なのでは? APNs: Apple Push Notifications Service, サーバからのリモート通知とかを担うサーバのこと 23
  18. 3. Live Activity • iOS 16.1(2022)でLive Activityが導入された • ActivityKitフレームワークを利用して、ライブアップデートを共有する ライブアクティビティを開始できる,

    最大8h生きる ActivityKit - Apple Developer より • iOSのロック画面+Dynamic Island, watchOSのスマートスタック, macOSのメニューバー, CarPlayのホーム画面に 表示 • APNs経由でLive Activityを更新できるようになった(iOS 17.1+) • Broadcast push notificationを利用して一斉に表示を書き換えられるようになった (iOS 18+) -> APNsを通じて、サーバからLive Activityを開始/更新/終了できる! -> 緊急地震速報が発表された時に、Live Activityで情報を表示できたら便利なのでは? APNs: Apple Push Notifications Service, サーバからのリモート通知とかを担うサーバのこと 24
  19. 3. Live Activity • iOS 16.1(2022)でLive Activityが導入された • ActivityKitフレームワークを利用して、ライブアップデートを共有する ライブアクティビティを開始できる,

    最大8h生きる ActivityKit - Apple Developer より • iOSのロック画面+Dynamic Island, watchOSのスマートスタック, macOSのメニューバー, CarPlayのホーム画面に 表示 • APNs経由でLive Activityを更新できるようになった(iOS 17.1+) • Broadcast push notificationを利用して一斉に表示を書き換えられるようになった (iOS 18+) -> APNsを通じて、サーバからLive Activityを開始/更新/終了できる! -> 緊急地震速報が発表された時に、Live Activityで情報を表示できたら便利なのでは? 緊急地震速報の震度/到達時刻の予想は、全国を約200に分割した予報区単位で発表される -> それぞれについて、Live ActivityのBroadcast Channelを作成してあげる APNs: Apple Push Notifications Service, サーバからのリモート通知とかを担うサーバのこと 25
  20. 3. Live Activity • Broadcast push notificationを利用して一斉に表示を書き換えられるようになった (iOS 18+) •

    Broadcast push notificationを使わない場合は、それぞれのLive ActivityごとにupdateTokenを取ってくる APNs: Apple Push Notifications Service, サーバからのリモート通知とかを担うサーバのこと 26
  21. 3. Live Activity • iOS 16.1(2022)でLive Activityが導入された • ActivityKitフレームワークを利用して、ライブアップデートを共有する ActivityKit

    - Apple Developer より ライブアクティビティを開始できる • iOSのロック画面+Dynamic Island, watchOSのスマートスタック, macOSのメニューバー, CarPlayのホーム画面に 表示 • APNs経由でLive Activityを更新できるようになった(iOS 17.1+) • Broadcast push notificationを利用して一斉に表示を書き換えられるようになった (iOS 18+) • Broadcast push notificationを使わない場合は、それぞれのLive ActivityごとにupdateTokenを取ってくる -> APNsを通じて、サーバからLive Activityを開始/更新/終了できる! -> 緊急地震速報が発表された時に、Live Activityで情報を表示できたら便利なのでは? APNs: Apple Push Notifications Service, サーバからのリモート通知とかを担うサーバのこと 27
  22. 4. iOSアプリ側の実装 • 実装の前に... • デザインを組みたい! • Claude Designとかを使って生成させても良かったが、どの情報をどこまで表示すればわかりやすいか自分で整理しながら 考えたかった

    • どうせ実装するときに色々修正するので、全体の方向性を定められれば良い • -> Figmaでコネコネ 30 ライブアクティビティ - HIG
  23. 4. iOSアプリ側の実装 • 実装! • アプリ側のInfo.plistをいじる • NSSupportsLiveActivities = YESにセッ

    トすることで、Live Activityをサポートし ていることを示す • APNs経由の高頻度の更新がある場合は、 • NSSupportsLiveActivitiesFrequentUpd ates = YESにセットする 34
  24. 4. iOSアプリ側の実装 • 実装! • Attributesの記述 • 静的データ: Live Activityを識別するための

    キー ActivityAttributesを実装 • ここでは、気象庁から提供される地震識別番 号(eventId: string) • 動的データ: 表示したい内容のデータ Activity.ContentState • 最大震度, 震源要素(M, 深さ), 地震発生時 刻, 現在地の予想震度 などなど • 合計で4KB未満にする必要あり 36
  25. 4. iOSアプリ側の実装 • 実装! • UIを記述 • さっき定義したAttributes構造体を使って、 ActivityConfigurationを定義していく •

    ロック画面, DynamicIsland(minimal, compact, expanded)のUIを記述していく • Xcode Previewsで確認 • アプリ本体でXcode Previewsを回すとFirebaseとかのビ ルドが走ってとっても遅い 38
  26. 4. iOSアプリ側の実装 • 実装! • UIを記述 • さっき定義したAttributes構造体を使って、 ActivityConfigurationを定義していく •

    ロック画面, DynamicIsland(minimal, compact, expanded)のUIを記述していく • Xcode Previewsで確認 • アプリ本体でXcode Previewsを回すとFirebaseとかのビ ルドが走ってとっても遅い • 39
  27. 4. iOSアプリ側の実装 • 実装! • UIを記述 • さっき定義したAttributes構造体を使って、 ActivityConfigurationを定義していく •

    ロック画面, DynamicIsland(minimal, compact, expanded)のUIを記述していく • Xcode Previewsで確認 • アプリ本体でXcode Previewsを回すとFirebaseとかのビ ルドが走ってとっても遅い • 40
  28. 4. iOSアプリ側の実装 • 実装! • UIを記述 • さっき定義したAttributes構造体を使って、 ActivityConfigurationを定義していく •

    ロック画面, DynamicIsland(minimal, compact, expanded)のUIを記述していく • Xcode Previewsで確認 • アプリ本体でXcode Previewsを回すとFirebaseとかのビ ルドが走ってとっても遅い • [TIPS] Preview用のTargetを作ってあげるといい👍 41
  29. 4. iOSアプリ側の実装 • 実装! • UIを記述 • さっき定義したAttributes構造体を使って、 ActivityConfigurationを定義していく •

    ロック画面, DynamicIsland(minimal, compact, expanded)のUIを記述していく • Xcode Previewsで確認 • アプリ本体でXcode Previewsを回すとFirebaseとかのビ ルドが走ってとっても遅い • [TIPS] Preview用のTargetを作ってあげるといい👍 👍 42
  30. 5. サーバとのつなぎ込み iOS 利用者端末 Widget Service Extension App サーバとのつなぎ込み! Swiftの世界

    Dartの世界 やらないといけないことは 1. Push-to-start tokenの取得と保存 2. 緊急地震速報発表時のペイロード送信 APNs Cloudflare EQMonitor Backend notification-sender notification-sender notification-sender 通知配信 通知配信 通知配信 valkey インメモリDB 44 notification-resolver 通知配信ペイロード 作成 eqmonitor-api API PostgreSQL RDB
  31. 4. iOSアプリ側の実装 iOS 利用者端末 前提 • デバイス固有のID (Device ID)と通知条件を Widget

    Service Extension Postgres(右下)に保管 実装の前に... • キャッシュ用のインメモリDB(左下)がいる • 全体構造について App Swiftの世界 Dartの世界 やらないといけないことは 1. Push-to-start tokenの取得と保存 APNs Cloudflare 2. 緊急地震速報発表時のペイロード送信 EQMonitor Backend notification-sender notification-sender notification-sender 通知配信 通知配信 通知配信 valkey インメモリDB 45 notification-resolver 通知配信ペイロード 作成 eqmonitor-api API PostgreSQL RDB
  32. 4. iOSアプリ側の実装 iOS 利用者端末 Dart FFI Widget Service Extension •

    実装の前に... • 全体構造について App Swiftの世界 Dartの世界 やらないといけないことは 1. Push-to-start tokenの取得と保存 APNs Cloudflare 2. 緊急地震速報発表時のペイロード送信 EQMonitor Backend notification-sender notification-sender notification-sender 通知配信 通知配信 通知配信 valkey インメモリDB Dart FFIを利用して、iOS側のAPIを呼び出し 46 notification-resolver 通知配信ペイロード 作成 eqmonitor-api API PostgreSQL RDB
  33. 4. iOSアプリ側の実装 iOS 利用者端末 Widget Service Extension • 実装の前に... •

    全体構造について App push-to-start token Swiftの世界 Dartの世界 やらないといけないことは 1. Push-to-start tokenの取得と保存 APNs Cloudflare 2. 緊急地震速報発表時のペイロード送信 EQMonitor Backend notification-sender notification-sender notification-sender 通知配信 通知配信 通知配信 valkey インメモリDB Dart FFIを利用して、iOS側のAPIを呼び出し 47 notification-resolver 通知配信ペイロード 作成 eqmonitor-api API PostgreSQL RDB
  34. 4. iOSアプリ側の実装 iOS 利用者端末 Widget Service Extension • 実装の前に... •

    全体構造について App Swiftの世界 Dartの世界 やらないといけないことは 1. Push-to-start tokenの取得と保存 APNs Cloudflare push-to-start token + Device ID 2. 緊急地震速報発表時のペイロード送信 EQMonitor Backend notification-sender notification-sender notification-sender 通知配信 通知配信 通知配信 valkey インメモリDB 48 notification-resolver 通知配信ペイロード 作成 eqmonitor-api API PostgreSQL RDB
  35. 4. iOSアプリ側の実装 iOS 利用者端末 Widget Service Extension • 実装の前に... •

    全体構造について App Swiftの世界 Dartの世界 やらないといけないことは 1. Push-to-start tokenの取得と保存 APNs Cloudflare 2. 緊急地震速報発表時のペイロード送信 EQMonitor Backend notification-sender notification-sender notification-sender 通知配信 通知配信 通知配信 notification-resolver 通知配信ペイロード 作成 eqmonitor-api API UPDATE ... SET ... WHERE device_id = ... valkey インメモリDB 49 PostgreSQL RDB
  36. 4. iOSアプリ側の実装 iOS 利用者端末 Widget Service Extension • 実装の前に... •

    全体構造について App Swiftの世界 Dartの世界 やらないといけないことは 1. Push-to-start tokenの取得と保存 APNs Cloudflare 2. 緊急地震速報発表時のペイロード送信 EQMonitor Backend notification-sender notification-sender notification-sender 通知配信 通知配信 通知配信 valkey インメモリDB 50 notification-resolver 通知配信ペイロード 作成 eqmonitor-api API PostgreSQL RDB
  37. 4. iOSアプリ側の実装 iOS 利用者端末 Widget Service Extension • 実装の前に... •

    全体構造について App Swiftの世界 Dartの世界 やらないといけないことは 1. Push-to-start tokenの取得と保存 APNs Cloudflare 2. 緊急地震速報発表時のペイロード送信 EQMonitor Backend notification-sender notification-sender notification-sender 通知配信 通知配信 通知配信 notification-resolver 通知配信ペイロード 作成 eqmonitor-api API 通知配信条件に一致したデバイスの push-to-start tokenの取得 51 valkey インメモリDB PostgreSQL RDB CACHE HIT CACHE MISS
  38. 4. iOSアプリ側の実装 iOS 利用者端末 Widget Service Extension • 実装の前に... •

    全体構造について App Swiftの世界 Dartの世界 やらないといけないことは 1. Push-to-start tokenの取得と保存 APNs Cloudflare 2. 緊急地震速報発表時のペイロード送信 EQMonitor Backend notification-sender notification-sender notification-sender 通知配信 通知配信 通知配信 valkey インメモリDB 52 notification-resolver 通知配信ペイロード 作成 eqmonitor-api API PostgreSQL RDB
  39. START curl --http2 -i \ -X POST "https://${deviceHost}/3/device/${pushToStartToken}" \ -H

    "authorization: bearer ${apnsJwt}" \ -H "apns-topic: ${bundleId}.push-type.liveactivity" \ -H "apns-push-type: liveactivity" \ -H "apns-priority: 10" \ -H "apns-expiration: 0" \ -H "content-type: application/json" \ --data-binary @- <<JSON { "aps": { "timestamp": $(date +%s), "event": “start", # start / update / end "attributes-type": "EewLiveActivityAttributes", "attributes": { # 静的データ部分 4. iOSアプリ側の実装 iOS 利用者端末 Widget Service Extension • 実装の前に... • 全体構造について App Swiftの世界 Dartの世界 "id": "${activityId}", "eventId": "${eventId}" やらないといけないことは }, "input-push-channel": “${channelId}", # Broadcast Push Notification Channel "alert": { "title": "【テスト】緊急地震速報", 1. Push-to-start tokenの取得と保存 APNs Cloudflare 2. 緊急地震速報発表時のペイロード送信 "body": "千葉県東方沖で地震 千葉県で強い揺れ", "sound": "default" }, "content-state": { # 動的データ部分 EQMonitor Backend "eventId": "${eventId}", "type": "eew", "headline": "千葉県東方沖で地震 千葉県で強い揺れ", notification-sender notification-sender notification-sender 通知配信 通知配信 通知配信 "hypocenterName": "千葉県東方沖", "magnitude": 5.8, "depth": 30, "time": "${originTime}", "isOriginTime": true, "maxIntensity": "5-", "serialNo": 1, "isFinal": false, "isWarning": true, "isCanceled": false, "isPlum": false, "isLevel": false, "isOnePoint": false, "location": { "regionName": "千葉県北東部", "forecastIntensity": "5-", "isWarning": true valkey インメモリDB 53 notification-resolver 通知配信ペイロード 作成 eqmonitor-api API PostgreSQL RDB
  40. START curl --http2 -i \ -X POST "https://${deviceHost}/3/device/${pushToStartToken}" \ -H

    "authorization: bearer ${apnsJwt}" \ -H "apns-topic: ${bundleId}.push-type.liveactivity" \ -H "apns-push-type: liveactivity" \ -H "apns-priority: 10" \ -H "apns-expiration: 0" \ -H "content-type: application/json" \ --data-binary @- <<JSON { "aps": { "timestamp": $(date +%s), "event": “start", # start / update / end "attributes-type": "EewLiveActivityAttributes", "attributes": { # 静的データ部分 4. iOSアプリ側の実装 iOS 利用者端末 Widget Service Extension • 実装の前に... • 全体構造について App Swiftの世界 Dartの世界 "id": "${activityId}", "eventId": "${eventId}" やらないといけないことは }, "input-push-channel": “${channelId}", # Broadcast Push Notification Channel "alert": { "title": "【テスト】緊急地震速報", 1. Push-to-start tokenの取得と保存 APNs Cloudflare 2. 緊急地震速報発表時のペイロード送信 "body": "千葉県東方沖で地震 千葉県で強い揺れ", "sound": "default" }, "content-state": { # 動的データ部分 EQMonitor Backend "eventId": "${eventId}", "type": "eew", "headline": "千葉県東方沖で地震 千葉県で強い揺れ", notification-sender notification-sender notification-sender 通知配信 通知配信 通知配信 "hypocenterName": "千葉県東方沖", "magnitude": 5.8, "depth": 30, "time": "${originTime}", "isOriginTime": true, "maxIntensity": "5-", "serialNo": 1, "isFinal": false, "isWarning": true, "isCanceled": false, "isPlum": false, "isLevel": false, "isOnePoint": false, "location": { "regionName": "千葉県北東部", "forecastIntensity": "5-", "isWarning": true valkey インメモリDB 54 notification-resolver 通知配信ペイロード 作成 eqmonitor-api API PostgreSQL RDB
  41. UPDATE curl --http2 -i \ -X POST "https://${broadcastHost}/4/broadcasts/apps/${bundleId}" \ -H

    "authorization: bearer ${apnsJwt}" \ -H "apns-channel-id: ${channelId}" \ # Broadcast Push Notification Channel -H "apns-push-type: liveactivity" \ -H "apns-priority: 10" \ -H "apns-expiration: 0" \ -H "content-type: application/json" \ --data-binary @- <<JSON { "aps": { "timestamp": $(date +%s), "event": “update", # Update "content-state": { "eventId": "${eventId}", "type": "eew", "headline": "千葉県東方沖で地震 千葉県 茨城県で強い揺れ", 4. iOSアプリ側の実装 iOS 利用者端末 Widget Service Extension • 実装の前に... • 全体構造について App Swiftの世界 Dartの世界 やらないといけないことは "hypocenterName": "千葉県東方沖", 1. Push-to-start tokenの取得と保存 APNs "magnitude": 6.2, "depth": 30, "time": "${originTime}", "isOriginTime": true, "maxIntensity": "5+", "serialNo": 2, "isFinal": false, "isWarning": true, "isCanceled": false, "isPlum": false, "isLevel": false, "isOnePoint": false, "location": { "regionName": "千葉県北東部", Cloudflare 2. 緊急地震速報発表時のペイロード送信 } } } EQMonitor Backend notification-sender notification-sender notification-sender 通知配信 通知配信 通知配信 notification-resolver 通知配信ペイロード 作成 eqmonitor-api API "forecastIntensity": "5+", "isWarning": true valkey インメモリDB } JSON 55 PostgreSQL RDB
  42. END curl --http2 -i \ -X POST "https://${broadcastHost}/4/broadcasts/apps/${bundleId}" \ -H

    "authorization: bearer ${apnsJwt}" \ -H "apns-channel-id: ${channelId}" \ # Broadcast Push Notification Channel -H "apns-push-type: liveactivity" \ -H "apns-priority: 10" \ -H "apns-expiration: 0" \ -H "content-type: application/json" \ --data-binary @- <<JSON { "aps": { "timestamp": $(date +%s), "event": “end", # End "content-state": { "eventId": "${eventId}", "type": "eew", "headline": "千葉県東方沖で地震 千葉県 茨城県で強い揺れ", 4. iOSアプリ側の実装 iOS 利用者端末 Widget Service Extension • 実装の前に... • 全体構造について App Swiftの世界 Dartの世界 やらないといけないことは "hypocenterName": "千葉県東方沖", 1. Push-to-start tokenの取得と保存 APNs "magnitude": 6.2, "depth": 30, "time": "${originTime}", "isOriginTime": true, "maxIntensity": "5+", "serialNo": 2, "isFinal": false, "isWarning": true, "isCanceled": false, "isPlum": false, "isLevel": false, "isOnePoint": false, "location": { "regionName": "千葉県北東部", Cloudflare 2. 緊急地震速報発表時のペイロード送信 } } } EQMonitor Backend notification-sender notification-sender notification-sender 通知配信 通知配信 通知配信 notification-resolver 通知配信ペイロード 作成 eqmonitor-api API "forecastIntensity": "5+", "isWarning": true valkey インメモリDB } JSON 56 PostgreSQL RDB
  43. 5. サーバとのつなぎ込み iOS 利用者端末 Widget Service Extension App サーバとのつなぎ込み! Swiftの世界

    Dartの世界 やらないといけないことは 1. Push-to-start tokenの取得と保存 2. 緊急地震速報発表時のペイロード送信 APNs Cloudflare EQMonitor Backend notification-sender notification-sender notification-sender 通知配信 通知配信 通知配信 valkey インメモリDB 57 notification-resolver 通知配信ペイロード 作成 eqmonitor-api API PostgreSQL RDB
  44. 5. サーバとのつなぎ込み iOS 利用者端末 Widget Service Extension App サーバとのつなぎ込み! Swiftの世界

    Dartの世界 やらないといけないことは 1. Push-to-start tokenの取得と保存 2. 緊急地震速報発表時のペイロード送信 APNs Cloudflare EQMonitor Backend 正しく運用できているか監視するために... notification-sender notification-sender notification-sender 通知配信 通知配信 通知配信 valkey インメモリDB 58 notification-resolver 通知配信ペイロード 作成 eqmonitor-api API PostgreSQL RDB
  45. 1. 個人開発しているアプリの紹介 • Flutter製 • API, WebSocket ServerはCloudflare Tunnel経由の Node

    • 通知配信は、Golang • FCM, APNsで通知配信 • インフラは、自宅サーバのkubernetes • (Oracle Cloudで冗長化) • ↑ APIとかのRolling Update APIのリクエストログ ↗ サーバのCPU/Mem使用率等監視 → 59
  46. 1. 個人開発しているアプリの紹介 • Flutter製 • API, WebSocket ServerはCloudflare Tunnel経由の Node

    • 通知配信は、Golang • FCM, APNsで通知配信 • インフラは、自宅サーバのkubernetes • (Oracle Cloudで冗長化) • ↑ 通知配信の成功状況の監視, APIパフォーマンスを監視 → 60
  47. まとめ • 緊急地震速報(警報)と緊急地震速報(予報)に分かれて発表されている • Live Activityを利用して、進行性のある情報をユーザへ提示できる • APNsを活用することで、サーバからLive Activityを開始/更新/終了できる •

    APNsのBroadcast Push Notification Capabilityを利用することで、一気に大量のデバイスへ update/endを送信することができる • 個別であれば、Service ExtensionからupdateTokenを取得してUpdate/Endできる • Apple Developerの動画はいいね • 暇な時に見ているといいアイディアを貰える • EQMonitorをぜひよろしくお願いします! 61
  48. 62