From 3bc35bad64a095dfb51a4440bf0d3684f9192564 Mon Sep 17 00:00:00 2001 From: hailin Date: Tue, 10 Mar 2026 02:42:34 -0700 Subject: [PATCH] feat(push): add offline push notification system (FCM + HMS + Mi + OPPO + vivo) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Backend (notification-service) - Add `device_push_tokens` table (migration 014) — stores per-user tokens per platform (FCM/HMS/MI/OPPO/VIVO) with UNIQUE constraint on (user_id, platform, device_id) - Add `DevicePushTokenRepository` with upsert/delete/targeting queries (by userId, tenantId, plan, tags, segment) - Add push provider interface with `sendBatch(tokens, message): BatchSendResult` returning `invalidTokens[]` for automatic DB cleanup - Add FCM provider — OAuth2 via RS256 JWT, chunked concurrency (max 20 parallel), detects UNREGISTERED/404 as invalid tokens - Add HMS provider — native batch API (1000 tokens/chunk), OAuth2 token cache with 5-min buffer, detects code 80100016 - Add Xiaomi provider — `/v3/message/regids` batch endpoint (1000/chunk), parses `bad_regids` field - Add OPPO provider — single-send with Promise-based mutex to prevent concurrent auth token refresh races - Add vivo provider — `/message/pushToList` batch endpoint, mutex same as OPPO, parses `invalidMap` - Add `OfflinePushService` — groups tokens by platform, sends concurrently, auto-deletes invalid tokens; fire-and-forget trigger after notification creation - Add `DevicePushTokenController` — POST/DELETE `/api/v1/notifications/device-token` - Wire offline push into `NotificationAdminController` and `EventTriggerService` - Add Kong route for device-token endpoint (JWT required) - Add all push provider env vars to docker-compose notification-service ## Flutter (it0_app) - Add `PushService` singleton — detects OEM (Huawei/Xiaomi/OPPO/vivo/FCM), initialises correct push SDK, registers token with backend - FCM: full init with background handler, foreground local notifications, tap stream, iOS APNs support - HMS: `HuaweiPush` async token via `onTokenEvent`, no FCM fallback on failure (Huawei without GMS cannot use FCM) - Mi/OPPO/vivo: MethodChannel bridge to Kotlin receivers; handler set before `getToken()` call to avoid race - `_initialized` guard prevents double-init on hot-restart - `static Stream onNotificationTap` for router navigation - Add Kotlin OEM bridge classes: `MiPushReceiver`, `OppoPushService`, `VivoPushReceiver` — forward token/message/tap events to Flutter via MethodChannel - Update `MainActivity` — register all three OEM MethodChannels; OEM credentials injected from `BuildConfig` (read from `local.properties` at build time) - Update `build.gradle.kts` — add Google Services + HMS AgConnect plugins, BuildConfig fields for OEM credentials, `fileTree("libs")` for OEM AARs - Update `android/build.gradle.kts` — add buildscript classpath for GMS + HMS, Huawei Maven repo - Update `AndroidManifest.xml` — HMS service, Xiaomi receiver + services, vivo receiver; OPPO handled via AAR manifest merge - Add OEM SDK AARs to `android/app/libs/`: MiPush 7.9.2, HeytapPush 3.7.1, vivo Push 4.1.3 - Add `google-services.json` (Firebase project: it0-iagent, package: com.iagent.it0_app) - Add `firebase_core ^3.6.0`, `firebase_messaging ^15.1.3`, `huawei_push ^6.11.0+300` to pubspec.yaml - Add `ApiEndpoints.notificationDeviceToken` endpoint constant ## Ops - Add FCM_PROJECT_ID, FCM_CLIENT_EMAIL, FCM_PRIVATE_KEY (+ HMS/Mi/OPPO/vivo placeholders) to `.env.example` with comments pointing to each OEM's developer portal Co-Authored-By: Claude Sonnet 4.6 --- deploy/docker/.env.example | 27 ++ deploy/docker/docker-compose.yml | 17 + it0_app/android/app/build.gradle.kts | 27 ++ it0_app/android/app/google-services.json | 29 ++ .../libs/MiPush_SDK_Client_7_9_2-C_3rd.aar | Bin 0 -> 1008221 bytes .../app/libs/com.heytap.msp_V3.7.1.aar | Bin 0 -> 132337 bytes .../app/libs/vpush_clientSdk_v4.1.3.0_513.aar | Bin 0 -> 245694 bytes .../android/app/src/main/AndroidManifest.xml | 41 +++ .../kotlin/com/iagent/it0_app/MainActivity.kt | 101 ++++-- .../com/iagent/it0_app/push/MiPushReceiver.kt | 66 ++++ .../iagent/it0_app/push/OppoPushService.kt | 61 ++++ .../iagent/it0_app/push/VivoPushReceiver.kt | 59 ++++ it0_app/android/build.gradle.kts | 14 + it0_app/lib/core/config/api_endpoints.dart | 3 + it0_app/lib/core/services/push_service.dart | 312 ++++++++++++++++++ it0_app/pubspec.yaml | 5 + packages/gateway/config/kong.yml | 12 + .../services/event-trigger.service.ts | 26 +- .../services/offline-push.service.ts | 123 +++++++ .../services/push-providers/fcm.provider.ts | 112 +++++++ .../services/push-providers/hms.provider.ts | 97 ++++++ .../services/push-providers/oppo.provider.ts | 98 ++++++ .../push-providers/push-provider.interface.ts | 21 ++ .../services/push-providers/vivo.provider.ts | 100 ++++++ .../push-providers/xiaomi.provider.ts | 69 ++++ .../entities/device-push-token.entity.ts | 13 + .../device-push-token.repository.ts | 150 +++++++++ .../device-push-token.controller.ts | 84 +++++ .../notification-admin.controller.ts | 28 +- .../src/notification.module.ts | 16 + .../migrations/014-add-device-push-tokens.sql | 19 ++ 31 files changed, 1694 insertions(+), 36 deletions(-) create mode 100644 it0_app/android/app/google-services.json create mode 100644 it0_app/android/app/libs/MiPush_SDK_Client_7_9_2-C_3rd.aar create mode 100644 it0_app/android/app/libs/com.heytap.msp_V3.7.1.aar create mode 100644 it0_app/android/app/libs/vpush_clientSdk_v4.1.3.0_513.aar create mode 100644 it0_app/android/app/src/main/kotlin/com/iagent/it0_app/push/MiPushReceiver.kt create mode 100644 it0_app/android/app/src/main/kotlin/com/iagent/it0_app/push/OppoPushService.kt create mode 100644 it0_app/android/app/src/main/kotlin/com/iagent/it0_app/push/VivoPushReceiver.kt create mode 100644 it0_app/lib/core/services/push_service.dart create mode 100644 packages/services/notification-service/src/application/services/offline-push.service.ts create mode 100644 packages/services/notification-service/src/application/services/push-providers/fcm.provider.ts create mode 100644 packages/services/notification-service/src/application/services/push-providers/hms.provider.ts create mode 100644 packages/services/notification-service/src/application/services/push-providers/oppo.provider.ts create mode 100644 packages/services/notification-service/src/application/services/push-providers/push-provider.interface.ts create mode 100644 packages/services/notification-service/src/application/services/push-providers/vivo.provider.ts create mode 100644 packages/services/notification-service/src/application/services/push-providers/xiaomi.provider.ts create mode 100644 packages/services/notification-service/src/domain/entities/device-push-token.entity.ts create mode 100644 packages/services/notification-service/src/infrastructure/repositories/device-push-token.repository.ts create mode 100644 packages/services/notification-service/src/interfaces/rest/controllers/device-push-token.controller.ts create mode 100644 packages/shared/database/src/migrations/014-add-device-push-tokens.sql diff --git a/deploy/docker/.env.example b/deploy/docker/.env.example index d8c73a2..bba2298 100644 --- a/deploy/docker/.env.example +++ b/deploy/docker/.env.example @@ -11,3 +11,30 @@ VAULT_MASTER_KEY=change-this-to-a-random-string TWILIO_ACCOUNT_SID= TWILIO_AUTH_TOKEN= TWILIO_PHONE_NUMBER= + +# Optional - Offline Push Notifications (notification-service) +# FCM (Firebase Cloud Messaging) — international Android + iOS +# Firebase Console > Project Settings > Service Accounts > Generate new private key +FCM_PROJECT_ID= +FCM_CLIENT_EMAIL= +FCM_PRIVATE_KEY= + +# HMS (Huawei Mobile Services) — Huawei / Honor devices +# AppGallery Connect > Project Settings > General information +HMS_APP_ID= +HMS_APP_SECRET= + +# Xiaomi Push — MIUI / Redmi devices +# Xiaomi Open Platform > Push > AppSecret +MI_APP_SECRET= + +# OPPO Push — OPPO / OnePlus / Realme devices +# OPPO Open Platform > Push > App Key & Master Secret +OPPO_APP_KEY= +OPPO_MASTER_SECRET= + +# vivo Push — vivo devices +# vivo Developer Platform > Push > App ID / Key / Secret +VIVO_APP_ID= +VIVO_APP_KEY= +VIVO_APP_SECRET= diff --git a/deploy/docker/docker-compose.yml b/deploy/docker/docker-compose.yml index 3b40d1d..d255a20 100644 --- a/deploy/docker/docker-compose.yml +++ b/deploy/docker/docker-compose.yml @@ -512,6 +512,23 @@ services: - DB_DATABASE=${POSTGRES_DB:-it0} - NOTIFICATION_SERVICE_PORT=3013 - JWT_SECRET=${JWT_SECRET:-dev-jwt-secret} + - REDIS_URL=redis://redis:6379 + # FCM (Firebase Cloud Messaging) — international Android + iOS + - FCM_PROJECT_ID=${FCM_PROJECT_ID:-} + - FCM_CLIENT_EMAIL=${FCM_CLIENT_EMAIL:-} + - FCM_PRIVATE_KEY=${FCM_PRIVATE_KEY:-} + # HMS (Huawei Mobile Services) — Huawei/Honor devices + - HMS_APP_ID=${HMS_APP_ID:-} + - HMS_APP_SECRET=${HMS_APP_SECRET:-} + # Xiaomi Push — MIUI/Redmi devices + - MI_APP_SECRET=${MI_APP_SECRET:-} + # OPPO Push — OPPO/OnePlus/Realme devices + - OPPO_APP_KEY=${OPPO_APP_KEY:-} + - OPPO_MASTER_SECRET=${OPPO_MASTER_SECRET:-} + # vivo Push — vivo devices + - VIVO_APP_ID=${VIVO_APP_ID:-} + - VIVO_APP_KEY=${VIVO_APP_KEY:-} + - VIVO_APP_SECRET=${VIVO_APP_SECRET:-} healthcheck: test: ["CMD-SHELL", "node -e \"require('http').get('http://localhost:3013/',r=>{process.exit(r.statusCode<500?0:1)}).on('error',()=>process.exit(1))\""] interval: 30s diff --git a/it0_app/android/app/build.gradle.kts b/it0_app/android/app/build.gradle.kts index 1e640ee..5b74ef3 100644 --- a/it0_app/android/app/build.gradle.kts +++ b/it0_app/android/app/build.gradle.kts @@ -7,6 +7,10 @@ plugins { id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") + // Google Services (FCM) + id("com.google.gms.google-services") + // Huawei AppGallery Connect (HMS Push) + id("com.huawei.agconnect") } // ============================================ @@ -56,6 +60,18 @@ android { targetSdk = flutter.targetSdkVersion versionCode = autoVersionCode versionName = getAutoVersionName() + + // OEM push credentials injected as BuildConfig fields. + // Set these in local.properties or CI environment variables. + val localProps = Properties().also { p -> + rootProject.file("local.properties").takeIf { it.exists() }?.let { + p.load(FileInputStream(it)) + } + } + buildConfigField("String", "MI_APP_ID", "\"${localProps.getProperty("MI_APP_ID", "")}\"") + buildConfigField("String", "MI_APP_KEY", "\"${localProps.getProperty("MI_APP_KEY", "")}\"") + buildConfigField("String", "OPPO_APP_KEY", "\"${localProps.getProperty("OPPO_APP_KEY", "")}\"") + buildConfigField("String", "OPPO_APP_SECRET", "\"${localProps.getProperty("OPPO_APP_SECRET", "")}\"") } buildTypes { @@ -65,10 +81,21 @@ android { signingConfig = signingConfigs.getByName("debug") } } + + buildFeatures { + buildConfig = true + } } dependencies { coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") + + // ── OEM Push SDKs ────────────────────────────────────────────────────────── + // AARs placed in android/app/libs/ (downloaded from each OEM's developer portal). + // Xiaomi Push SDK: https://dev.mi.com/distribute/doc/details?pId=1479 + // OPPO HeytapPush SDK: https://open.oppomobile.com/new/developmentDoc/info?id=11212 + // vivo Push SDK: https://dev.vivo.com.cn/documentCenter/doc/332 + implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar")))) } flutter { diff --git a/it0_app/android/app/google-services.json b/it0_app/android/app/google-services.json new file mode 100644 index 0000000..0f18be8 --- /dev/null +++ b/it0_app/android/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "673907592399", + "project_id": "it0-iagent", + "storage_bucket": "it0-iagent.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:673907592399:android:43728c5c81378e633efbba", + "android_client_info": { + "package_name": "com.iagent.it0_app" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyCfEqqlDL4ndfViHS8AX2zztwrsdUYWysw" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/it0_app/android/app/libs/MiPush_SDK_Client_7_9_2-C_3rd.aar b/it0_app/android/app/libs/MiPush_SDK_Client_7_9_2-C_3rd.aar new file mode 100644 index 0000000000000000000000000000000000000000..00d45a473903e52f2ec0f6f5c664514acbdec53c GIT binary patch literal 1008221 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