Skip to content

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

Description

@denny0223

OPass 預計以中央 Push Gateway 與 Firebase Cloud Messaging(FCM)取代 OneSignal。本 issue 用於讓 CCIP-Android 開發者確認架構影響、預計改動與驗收範圍並提出回饋;尚未開始實作。下列 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
  • 程式碼檢視基準:36ef95b17cfcc2ad6ca192b891045298cb5e4b4b

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

現況與風險

  • CCIPApplication 在 App 啟動時初始化 OneSignal,並註冊 NotificationClickListener
  • TokenCheckFragment 只有在 /status 驗證成功後才儲存 token、role 並加入 <EVENT_ID><ROLE> tag,符合「登入成功後才訂閱」的產品規則。
  • 成功登入新身分時不會移除舊 OneSignal tag。過去向「全體」發送時,OneSignal 可能將多個符合條件的 tag 合併成同一位收件者;改成每個 FCM role topic 各送一則後,殘留訂閱可能造成重複通知。
  • token 與 role 已依活動分開儲存,符合每個已登入活動各自訂閱的需求。FCM topic 狀態也必須依 EVENT_ID 儲存;同一活動重新登入不同身分時,只替換該活動的舊 topic。
  • App 透過 AppCompat 的 per-app locale 切換語言;LocaleUtil 可取得套用後的實際 locale。
  • AndroidManifest 尚未直接宣告 POST_NOTIFICATIONS。移除 OneSignal 後不能再依賴 manifest merge 間接提供。
  • 現有 session_bookmark channel 是議程提醒,不應拿來顯示活動公告推播。

需求與限制

  • 只支援採用新契約的 App 版本,不做 OneSignal 雙送或舊版遷移。
  • 使用者只有成功登入後才訂閱 topic;通知權限不影響訂閱資格。
  • Android 目前沒有登出功能。再次成功登入同一活動的不同身分時,新身分只取代該活動的舊身分。
  • 每個 App 安裝實例可同時訂閱多個活動,但同一個 EVENT_ID 至多保留一個 OPass topic。
  • topic 格式為 opass-v1.<EVENT_ID>.<ROLE>.<PUSH_LOCALE>,不建立 .all topic。
  • App 不儲存 Gateway key、Firebase service-account credential,也不把 FID 或 registration token 上傳到 Gateway。

1. SDK 與既有 OneSignal 清理

  • 以現有 version catalog 加入 Firebase Messaging;沿用既有 google-services.json、Google Services plugin 與 Firebase Analytics。
  • 移除 OneSignal dependency、版本宣告與 import。
  • 移除 CCIPApplication 的 OneSignal 初始化與 click listener 註冊。
  • 移除 AndroidManifest 的 OneSignal metadata。
  • 刪除 OneSignal 專用的 NotificationClickListener
  • 明確在 AndroidManifest 宣告 POST_NOTIFICATIONS;目前可能由 OneSignal dependency 合併提供,移除 dependency 後不可再依賴它。
  • AndroidManifest.xml 設定 firebase_messaging_installation_id_enabled=true,採用 Firebase Messaging 目前的 FID registration 流程,不沿用已棄用的 registration token API。
  • 確認 Firebase Messaging 與 Analytics 都連到現有 opass-8b7db 專案,不建立第二套 Firebase 設定。

2. Topic 推導與訂閱同步

  • 建立單一、小型的 topic manager,集中處理 topic 推導、訂閱與取消訂閱;不建立自製 queue 或 device registry。
  • 驗證 EVENT_IDROLE 均符合 [A-Za-z0-9_-]{1,64},並拒絕角色 all
  • 依下列規則將 App locale 對應至推播語系:
    • zh-Hans-*zh-Hans
    • zh-Hant-*zh-Hant
    • nan-Hant-*nan-Latn-*zh-Hant
    • x-default → 先解析目前系統 locale,再套用相同規則
    • 其他語言 → en
  • 在 App-wide preference 儲存 EVENT_ID 到目前 topic 的對應;不得只保存單一 topic。
  • 根據所有已登入活動的 token 與 role 推導目標 topic 集合;同一活動的目標與目前 topic 相同時不呼叫 FCM。
  • 某活動的目標 topic 改變時,只取消該活動的舊 topic,再訂閱新 topic;只有訂閱成功後才更新該活動的本機狀態。
  • 失敗時保留足供下次同步重試的狀態,不得讓同一活動永久留下未記錄的第二個 topic;訂閱操作本身的持久化重試交給 Firebase Messaging SDK。

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

  • TokenCheckFragment 驗證身分成功並儲存新 token 與 role 後。
  • App 啟動並讀取登入狀態後,核對所有已登入活動;切換目前活動本身不得取消其他活動的 topic。
  • App 語系變更後,更新所有已登入活動的 topic。
  • Firebase Messaging registration/FID 完成或更新時。

3. 通知顯示、權限與點擊

  • 在 App 啟動時建立固定 ID 為 announcementsIMPORTANCE_DEFAULT 且使用預設提示音的公開活動推播 NotificationChannel
  • 在 AndroidManifest 將 announcements 設為 FCM default notification channel,並指定符合 Android 規範的 default notification icon,確保背景通知不落入其他 channel。
  • 增加 FirebaseMessagingService 並在 AndroidManifest 宣告 com.google.firebase.MESSAGING_EVENT intent filter,讓 App 在前景時也能顯示 notification message。
  • FirebaseMessagingService.onRegistered() 收到 FID registration 完成或更新時觸發 topic 同步;不得把 FID 上傳到 Gateway。
  • App 在背景或未執行時使用 FCM notification message 的系統顯示路徑,不改成依賴背景執行的 data-only notification。
  • 使用 FCM normal priority,不由推播 payload 管理或累加 badge。
  • LauncherActivity 讀取背景通知點擊帶入的 data;前景通知建立的 PendingIntent 也走同一條路徑,不維護兩套解析邏輯。
  • 只有 payload 同時包含 push_idevent_id 時才視為 Gateway 推播通知。uri 為 HTTPS 時開啟該 URI;沒有 uri 時切換至 event_id 對應的活動並進入公告頁。
  • 將登入成功後的 OneSignal 權限請求改成既有程式已採用的 ActivityResultContracts.RequestPermission
  • 使用者拒絕通知權限時仍維持正確 topic 訂閱;保留登入提示與議程提醒既有的個別互動,不把兩者混成同一個設定。

4. Analytics 與驗證

  • 確認 Gateway 將 push_id 用作 FCM Analytics label;Android 只將它用於通知點擊辨識,不另造識別碼。
  • 確認背景 notification message 的送達、顯示與開啟事件會進入 Firebase reporting。前景自行顯示的通知若沒有 Firebase 官方支援的對應統計,不自製看似相同的指標。
  • 為語系對應、topic 建構與訂閱同步的狀態轉換加入最小單元測試。
  • 加入最小必要的 unit test 設定;自動化測試不得訂閱正式 topic 或送出真實通知。
  • 執行 ./gradlew testDebugUnitTest lintDebug assembleDebug,並記錄無法在本機執行的驗證。

人工驗收矩陣:

  • 未登入、首次登入、相同身分再次登入。
  • 同活動切換角色只保留新角色;切換活動或切換回曾登入活動時,其他已登入活動仍可收訊。
  • zh-Hantzh-Hans、英文 fallback 與 x-default
  • 切換語言後更新所有活動;同活動切換身分後不會再收到該活動舊角色 topic 的推播。
  • 離線失敗後重新啟動可完成訂閱同步。
  • App 在前景、背景與未執行時均符合預期。
  • 收到非目前活動且沒有 uri 的通知時,點擊後開啟 event_id 對應活動的公告頁。
  • uri、無 uri、拒絕通知權限三種路徑。
  • Firebase Console 的 Android Sends、Received、Impressions 與 Opens 符合文件所述限制。
  • 同一裝置從舊版本直接升級後,可為所有已登入活動建立正確的 FCM topic 訂閱;不要求 OneSignal 舊版繼續收訊。

不在本 issue 範圍

  • device registry 或 registration token 上傳 API。
  • App 內的 Gateway key、service-account credential 或發布功能。
  • .all topic、逐裝置發送、OneSignal 相容層。
  • 為這次遷移新增登出 UI。

請協助回饋

  • 上述登入、活動切換與語系切換流程是否符合目前維護者的理解。
  • 每個已登入活動各一個 topic 的同步時機,是否遺漏其他 Android lifecycle 路徑。
  • 通知顯示、點擊、權限、Analytics 或測試範圍是否有平台限制未納入。

參考資料

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions