Skip to content

[RFC] 配合 OPass Push Gateway 改用 FCM topic 推播 #68

Description

@denny0223

OPass 預計以中央 Push Gateway 與 Firebase Cloud Messaging(FCM)取代 OneSignal。本 issue 用於讓 CCIP-iOS 開發者確認架構影響、預計改動與驗收範圍並提出回饋;尚未開始實作。下列 checkbox 用於界定預計工作,不表示已排程。

架構摘要

CCIP-Admin-Bueno -> OPass Push Gateway -> FCM topic -> CCIP-Android / CCIP-iOS
  • Gateway 由 OPass 團隊維運;活動由驗證成功的 Gateway key 決定,呼叫端不能指定 EVENT_ID 或完整 topic。
  • CCIP-Server 不取得 Gateway key,也不參與推播發送。
  • 推播內容一定是公開資訊;不建立 device registry,也不逐一向裝置發送。
  • App 只有在活動登入成功後才訂閱;每個已登入活動各保留一個角色/推播語系 topic,切換目前活動不會取消其他活動的訂閱。

實作依據

  • Gateway 契約:ADR 0001(revision 52b9aab
  • 程式碼檢視基準:11edb506834e7772fbb74607822df202b06ea2b9

若實作需要改變 topic、payload 或跨 repository 責任,應先更新 Gateway 契約,再調整本 issue。

現況與風險

  • OPassApp.swift 在 App 啟動時初始化 OneSignal,並立即要求通知權限。
  • EventStore.redeem 只有在身分驗證成功後才加入 <EVENT_ID><ROLE> tag,符合「登入成功後才訂閱」的產品規則。
  • 登入新身分時,只有現有 tag 數量至少為 2 才全部移除;恰好有一個舊 tag 時會留下舊身分。signOut 也只是把 tag value 設為空字串。改成每個 FCM role topic 各送一則後,殘留訂閱可能造成重複通知。
  • iOS 已有登出功能;目前活動、token 與 role 會分別儲存在 NSUbiquitousKeyValueStore、可同步 Keychain 與 per-event UserDefaults。每個活動的 FCM topic 對應是單一裝置的狀態,不得跟著 iCloud 或 Keychain 同步到其他裝置。
  • 切換活動時可以回到過去已登入的活動,因此切換目前活動不得取消先前活動的訂閱。
  • 專案已連接與 Android 相同的 Firebase 專案 opass-8b7db,但 App target 尚未加入 Firebase Messaging product。既有 SDK 已提供 Analytics、App Check、Crashlytics 與 Performance。
  • OneSignal 另有 Swift Package、Notification Service Extension target、App Group entitlement 與 Diagnostic 畫面;目前程式碼未發現其他 App Group 用途。

1. Firebase Messaging 與 OneSignal 清理

  • 從現有 Firebase Apple SDK package 加入 Firebase Messaging product,不建立第二套 Firebase 設定。
  • 在 Firebase Console 確認 app.opass.ccip 的 APNs authentication key 可供開發與正式環境使用。
  • 移除 OneSignal 初始化、Swift Package product、import 與 OneSignal-XCFramework package reference。
  • 移除 OneSignal Notification Service Extension target、embed 設定與 extension 原始碼;本契約不傳送需要 service extension 的 rich media。
  • 確認沒有其他用途後,移除主 App 與 extension 的 group.app.opass.ccip App Group entitlement。
  • 保留 remote-notification background mode,供 FCM callback 與 delivery metrics 使用。
  • 移除 Diagnostic 畫面的 OneSignal ID,不改成顯示或上傳 FCM registration/FID。

2. APNs、FCM registration 與通知權限

  • 讓既有 SwiftUI AppDelegate 同時擔任 UNUserNotificationCenterDelegate 與 Firebase Messaging delegate。
  • 在 App 的 Info.plist 設定 FirebaseMessagingInstallationIdEnabled = YES,採用 Firebase Messaging 目前的 FID registration 流程,不沿用已棄用的 registration token API。
  • 設定兩個 delegate、呼叫 registerForRemoteNotifications(),並在 APNs registration callback 明確把 APNs token 交給 Firebase Messaging;SwiftUI App 不依賴隱含 swizzling 完成這一步。
  • 在 Firebase Messaging registration/FID callback 觸發 topic 同步,但不得把 registration、FID 或 APNs token 上傳到 Gateway。
  • 先維持現行行為:App 啟動時要求通知權限。若團隊要改成登入成功後才詢問,另作產品決策,不與「登入成功後才訂閱 topic」混為一談。
  • 使用者拒絕通知權限時仍維持正確 topic 訂閱,讓日後在系統設定開啟權限後可直接收訊。

3. Topic 推導與訂閱同步

  • 建立單一、小型的 topic manager,集中處理 topic 推導、訂閱與取消訂閱;不建立自製 queue 或 device registry。
  • 驗證 EVENT_IDROLE 均符合 [A-Za-z0-9_-]{1,64},並拒絕角色 all
  • 依 App 實際採用的 localization 推導推播語系:zh-Hantnan 使用 zh-Hantzh-Hans 使用 zh-Hans,其他使用 en
  • 在裝置本機的 UserDefaults.standard 儲存 EVENT_ID 到目前 topic 的對應;不可放入 NSUbiquitousKeyValueStore、synchronizable Keychain 或 per-event defaults。
  • 根據所有已登入活動的 token 與 role 推導目標 topic 集合。某活動資料不完整或登出時,只移除該活動的 topic;同一活動的目標與目前 topic 相同時不呼叫 FCM。
  • 某活動的目標改變時,只取消該活動的舊 topic,再訂閱新 topic;只有新訂閱成功後才更新該活動的本機狀態。失敗時保留足供下次同步重試的狀態。
  • 在 Firebase Messaging 已完成 registration 後,從主執行緒呼叫 topic API;SDK 的持久化重試交給 Firebase Messaging,不另做 background worker。

至少在下列時機同步訂閱:

  • EventStore.redeem 驗證身分並儲存新 attendee、token 與 role 後。
  • EventStore.signOut 清除登入資料時,只移除該活動的 topic。
  • App 啟動並完成 OPassStore.loadEvent 後,核對所有已登入活動;切換目前活動本身不得取消其他活動的 topic。
  • Firebase Messaging registration/FID 更新時,核對所有已登入活動。
  • App 語言變更後重新啟動時,更新所有已登入活動的 topic。

4. 前景顯示與通知點擊

  • UNUserNotificationCenterDelegate 的前景 callback 顯示 list、banner 與預設提示音。
  • 在通知點擊 callback 只處理同時包含 push_idevent_id 的 Gateway 通知。
  • uri 是 HTTPS 時交由系統開啟;沒有 uri 時,切換至 event_id 對應的活動,再沿用現有 RouterFeatureDestinations.announcement 進入公告頁。
  • 使用原生 NotificationCenter 或既有狀態傳遞通知點擊,不新增第二套 navigation framework。
  • 不由 App 或 Gateway 管理、累加 badge。

5. Analytics 與驗證

  • 保留既有 Firebase Analytics,確認 Firebase data sharing 已啟用。
  • 在官方指定的 remote-notification callback 呼叫 exportDeliveryMetricsToBigQuery;不得把完整 payload、token 或 FID 寫入 log。
  • 讓 Firebase SDK 處理 notification received/opened Analytics;只有停用 swizzling 時才依官方文件手動呼叫 appDidReceiveMessage
  • 為語系對應、topic 建構與訂閱同步的狀態轉換加入最小單元測試。
  • 在實體裝置驗證開發與正式 APNs 環境;模擬器結果不能取代實體裝置驗收。
  • 用 Xcode 建置 App target,確認移除 extension target 後 archive、簽章與 embed 設定仍正確。

人工驗收:

  • 未登入、首次登入、相同身分再次登入、切換身分與登出。
  • 切換活動及切回曾經登入的活動,確認其他已登入活動仍可收訊。
  • zh-Hantnan、英文 fallback;目前沒有 zh-Hans App localization,確認不會誤判。
  • 切換語言後更新所有活動;同活動切換身分後不會再收到該活動舊角色 topic 的推播;切換活動則保留其他活動的訂閱。
  • App 在前景、背景與被終止時,有 uri、無 uri 的點擊結果都正確。
  • 收到非目前活動且沒有 uri 的通知時,點擊後開啟 event_id 對應活動的公告頁。
  • 拒絕通知權限不會破壞 topic 狀態;重新開啟權限後可以收訊。
  • Firebase Console 與 BigQuery 顯示的 Apple Sends、Opens 與 delivery metrics 符合契約所述限制。

不在本 issue 範圍

  • registration、FID 或 APNs token 上傳 API。
  • App 內的 Gateway key、service account credential 或發布功能。
  • .all topic、逐裝置發送、OneSignal 相容層或新的 notification service extension。
  • 為這次遷移重構登入資料、iCloud 同步或整套 navigation architecture。

請協助回饋

  • 上述每個已登入活動各保留一個 topic,以及登入、登出、活動切換與本機/同步儲存邊界,是否符合目前維護者的理解。
  • 是否同意維持現行做法,在 App 啟動時詢問通知權限,但只在登入成功後訂閱 topic。
  • OneSignal extension、App Group、非目前活動的通知點擊、Analytics 或實體裝置驗收是否有平台限制未納入。

參考資料

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions