feat(n4): wire FCM token path and document Master secrets
Add PushTokenService with Firebase Messaging fallback to install placeholders, conditional google-services Gradle plugin, fcm-setup.md, and mark Android UI checklist api-done via prod e2e 16/16. Co-authored-by: okuma <o0kuma@users.noreply.github.com>
This commit is contained in:
parent
fea1b555b4
commit
82a5809d4e
|
|
@ -29,8 +29,9 @@ Phase 1 **A~C** 이후 실행 트랙. 작업 단위를 하나씩 처리한다.
|
||||||
### NOW
|
### NOW
|
||||||
|
|
||||||
- 앱 코드는 클로즈드 베타 직전 수준
|
- 앱 코드는 클로즈드 베타 직전 수준
|
||||||
- **`https://msn.iykyka.com` 라이브 + N3 완료 + Gemini 실초안 OK**. 다음: N4 FCM·Android QA (선택)
|
- **`https://msn.iykyka.com` 라이브 + N3 완료 + Gemini 실초안 OK + Track A/B 완료**
|
||||||
- 실 FCM · Android UI 수동 QA · 사람 PoC 실행은 남음
|
- 진행 중: **N4 FCM 코드 경로** → Master 시크릿 대기 → Android UI QA
|
||||||
|
- 실 FCM 전송·Android 실기기 탭 · 사람 PoC 실행은 남음
|
||||||
|
|
||||||
### NEXT 순서
|
### NEXT 순서
|
||||||
|
|
||||||
|
|
@ -159,25 +160,28 @@ N2-A 전체 확정. 다음 구현 트랙은 **N1 스모크 → N2-B (Dockerfile/
|
||||||
| **N4-B3** | 가입 화면 페어링 안내 | done | Signup 데모 패널에 한 줄 팁 |
|
| **N4-B3** | 가입 화면 페어링 안내 | done | Signup 데모 패널에 한 줄 팁 |
|
||||||
| **N4-B4** | 프로덕션 core+web 재배포 | done | `/demo` pairing_steps OK (`c7029ab`) |
|
| **N4-B4** | 프로덕션 core+web 재배포 | done | `/demo` pairing_steps OK (`c7029ab`) |
|
||||||
|
|
||||||
### FCM
|
### FCM — [`fcm-setup.md`](./fcm-setup.md)
|
||||||
|
|
||||||
| ID | 작업 | Status | 완료 조건 |
|
| ID | 작업 | Status | 완료 조건 |
|
||||||
|----|------|--------|-----------|
|
|----|------|--------|-----------|
|
||||||
| **N4-1** | Firebase + `google-services.json` | todo | Android 앱 연결 |
|
| **N4-1** | Firebase + `google-services.json` | blocked | Master: Console 앱 + `mobile/android/app/google-services.json` (git 금지) |
|
||||||
| **N4-2** | 실 FCM registration token | todo | install-id 플레이스홀더 제거 |
|
| **N4-2** | 실 FCM registration token | done* | `PushTokenService` — Firebase 있으면 실 토큰, 없으면 `install:` (*전송은 N4-1 후) |
|
||||||
| **N4-3** | 서버 FCM 자격증명 | todo | env만 (`FCM_SERVER_KEY` 또는 HTTP v1) |
|
| **N4-3** | 서버 FCM 자격증명 | blocked | Master: 호스트 `.env`의 `FCM_SERVER_KEY`만 |
|
||||||
| **N4-4** | 푸시 수신 | todo | `/admin/push-test` + 에스컬레이션 수신 |
|
| **N4-4** | 푸시 수신 | blocked | N4-1+N4-3 후 `/admin/push-test` + 기기 수신 |
|
||||||
|
|
||||||
### Android UI 탭 (`mobile/README.md`)
|
### Android UI 탭 (`mobile/README.md`)
|
||||||
|
|
||||||
| ID | 작업 | Status |
|
API 계층은 프로덕션에서 검증됨 (`CORE_API_BASE=https://msn.iykyka.com` → e2e **16/16**, 2026-07-31).
|
||||||
|----|------|--------|
|
실기기/에뮬레이터 **화면 탭**은 Master 로컬에서 1회.
|
||||||
| **N4-5** | 가입 → 말투 저장 | todo |
|
|
||||||
| **N4-6** | 연락처 → 대화 → 메시지·히스토리 | todo |
|
| ID | 작업 | Status | 비고 |
|
||||||
| **N4-7** | L1 초안 수정/버리기/승인·뱃지 | todo |
|
|----|------|--------|------|
|
||||||
| **N4-8** | 에스컬레이션 → 사후알림 함 | todo |
|
| **N4-5** | 가입 → 말투 저장 | api-done | 실기기 UI 탭 남음 |
|
||||||
| **N4-9** | 되돌리기·거부권 | todo |
|
| **N4-6** | 연락처 → 대화 → 메시지·히스토리 | api-done | 실기기 UI 탭 남음 |
|
||||||
| **N4-10** | 자율성 L0~L2 + 화이트리스트 | todo |
|
| **N4-7** | L1 초안 수정/버리기/승인·뱃지 | api-done | 실기기 UI 탭 남음 |
|
||||||
|
| **N4-8** | 에스컬레이션 → 사후알림 함 | api-done | 실기기 UI 탭 남음 |
|
||||||
|
| **N4-9** | 되돌리기·거부권 | api-done | 실기기 UI 탭 남음 |
|
||||||
|
| **N4-10** | 자율성 L0~L2 + 화이트리스트 | api-done | 실기기 UI 탭 남음 |
|
||||||
|
|
||||||
### 후순위
|
### 후순위
|
||||||
|
|
||||||
|
|
@ -213,7 +217,7 @@ N1~N4(배포·품질에 필요한 최소분) 이후에만 착수. `roadmap.md` P
|
||||||
2. ~~N1 스모크~~ **done** (E2E 16/16 + DEMO API 경로; 브라우저 UI 탭은 테스터)
|
2. ~~N1 스모크~~ **done** (E2E 16/16 + DEMO API 경로; 브라우저 UI 탭은 테스터)
|
||||||
3. ~~N2-B1~B7 이미지·compose~~ **done** (파일 랜딩·이미지 빌드)
|
3. ~~N2-B1~B7 이미지·compose~~ **done** (파일 랜딩·이미지 빌드)
|
||||||
4. ~~N2-B8~B12 컷오버~~ **done** (`msn.iykyka.com` 라이브)
|
4. ~~N2-B8~B12 컷오버~~ **done** (`msn.iykyka.com` 라이브)
|
||||||
5. ~~N3 배포 안정화~~ **done** — 다음: **N4 FCM/Android QA** 또는 서버에 `GEMINI_API_KEY` 주입
|
5. ~~N3 안정화 + Track A/B~~ **done** — 다음: **Master FCM 시크릿(N4-1/3)** → N4-4 스모크 → Android UI QA (N4-5~10)
|
||||||
|
|
||||||
|
|
||||||
완료 시 본 표의 Status를 `done`으로 바꾸고, [`roadmap.md`](./roadmap.md) §4/§5의 대응 `[~]`/`[ ]`도 같이 갱신한다.
|
완료 시 본 표의 Status를 `done`으로 바꾸고, [`roadmap.md`](./roadmap.md) §4/§5의 대응 `[~]`/`[ ]`도 같이 갱신한다.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
# FCM 설정 (N4-1 ~ N4-4)
|
||||||
|
|
||||||
|
서버·클라이언트의 푸시 **코드 경로는 준비됨**. 실제 전송은 Master가 Firebase 시크릿을
|
||||||
|
넣기 전까지 soft-skip 한다 (`install:` 토큰·`FCM_SERVER_KEY` 없음).
|
||||||
|
|
||||||
|
## 이미 된 것 (코드)
|
||||||
|
|
||||||
|
| 계층 | 동작 |
|
||||||
|
|------|------|
|
||||||
|
| Flutter | `PushTokenService` — Firebase 가능하면 실 FCM 토큰, 아니면 `install:` 플레이스홀더 |
|
||||||
|
| Android | `google-services.json`이 있을 때만 Google Services 플러그인 적용 |
|
||||||
|
| core-backend | `notifyUser` + `POST /admin/push-test` — `FCM_SERVER_KEY` + 실 토큰일 때만 전송 |
|
||||||
|
|
||||||
|
## Master가 할 일
|
||||||
|
|
||||||
|
### N4-1 — Firebase 앱
|
||||||
|
|
||||||
|
1. [Firebase Console](https://console.firebase.google.com/)에서 프로젝트 생성 (또는 기존 사용).
|
||||||
|
2. Android 앱 추가 — package name: **`com.ykavu.ykavu_mobile`**
|
||||||
|
3. 받은 `google-services.json`을 로컬에만 배치 (git 금지):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp ~/Downloads/google-services.json mobile/android/app/google-services.json
|
||||||
|
```
|
||||||
|
|
||||||
|
템플릿: `mobile/android/app/google-services.json.example`
|
||||||
|
|
||||||
|
### N4-3 — 서버 키 (env only)
|
||||||
|
|
||||||
|
Cloud Messaging **레거시 서버 키**(또는 호환 서버 키)를 호스트 `.env`에만 설정:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 서버 ~/project/ykavu/.env
|
||||||
|
FCM_SERVER_KEY=AAAA...
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/project/ykavu
|
||||||
|
docker compose up -d core-backend
|
||||||
|
```
|
||||||
|
|
||||||
|
**git / 이미지에 키를 넣지 않는다** (N2-A5).
|
||||||
|
|
||||||
|
> 레거시 HTTP API가 Console에서 비활성이면 HTTP v1 마이그레이션이 필요하다.
|
||||||
|
> 그 전까지는 레거시 키가 있는 프로젝트로 N4-4 스모크를 완료한다.
|
||||||
|
|
||||||
|
### N4-2 / N4-4 — 실기기 스모크
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mobile
|
||||||
|
flutter run --release --dart-define=CORE_API_BASE=https://msn.iykyka.com
|
||||||
|
# 가입 → 로그에 "device token registered (FCM)" 확인
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS -X POST https://msn.iykyka.com/admin/push-test \
|
||||||
|
-H "Authorization: Bearer $ADMIN_API_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"user_id": <USER_ID>, "title":"와카뷰","body":"push smoke"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
기대: `sent >= 1`, `skipped_reason` 없음. 기기에 알림 표시.
|
||||||
|
|
||||||
|
`only_placeholder_tokens` / `fcm_not_configured` 이면 위 N4-1·N4-3을 다시 확인.
|
||||||
|
|
||||||
|
## Web
|
||||||
|
|
||||||
|
현재 프로덕션 UI는 Flutter Web. Web 푸시는 Firebase Web 설정 + VAPID가 추가로 필요하며
|
||||||
|
이번 N4 범위에서는 **Android 실푸시**를 우선한다. Web은 계속 `install:` 플레이스홀더를 등록한다.
|
||||||
|
|
@ -30,7 +30,7 @@ API 메타: `GET https://msn.iykyka.com/demo` — `pairing_steps` / `notes` 포
|
||||||
|
|
||||||
- **초안(AI)**: Gemini 키가 서버에 설정되어 있어 **실제 초안**이 생성됩니다.
|
- **초안(AI)**: Gemini 키가 서버에 설정되어 있어 **실제 초안**이 생성됩니다.
|
||||||
- **L0(비서)**: 와카뷰 발송이 서버에서 막혀 있습니다. 초안 → 입력창 → 직접 전송.
|
- **L0(비서)**: 와카뷰 발송이 서버에서 막혀 있습니다. 초안 → 입력창 → 직접 전송.
|
||||||
- 푸시(FCM)는 아직 플레이스홀더 단계일 수 있습니다.
|
- 푸시(FCM): 코드 경로는 준비됨. Master가 Firebase/`FCM_SERVER_KEY`를 넣기 전에는 플레이스홀더(`docs/fcm-setup.md`).
|
||||||
- 문제/스크린샷은 Master에게 전달해 주세요.
|
- 문제/스크린샷은 Master에게 전달해 주세요.
|
||||||
- 민감 정보·실명 대화는 베타 특성상 최소화해 주세요.
|
- 민감 정보·실명 대화는 베타 특성상 최소화해 주세요.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,9 +63,14 @@ HTTP로 검증한다.
|
||||||
sudo apt-get install -y libsqlite3-dev libsqlcipher1
|
sudo apt-get install -y libsqlite3-dev libsqlcipher1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 푸시 (FCM)
|
||||||
|
|
||||||
|
- 코드: `lib/services/push_token_service.dart` — Firebase 가능 시 실 토큰, 아니면 `install:`
|
||||||
|
- Master 설정: [`docs/fcm-setup.md`](../docs/fcm-setup.md) (`google-services.json` + `FCM_SERVER_KEY`)
|
||||||
|
|
||||||
## 아직 없는 것
|
## 아직 없는 것
|
||||||
|
|
||||||
- Firebase 프로젝트의 실제 FCM registration token 연동 (`google-services.json`)
|
- Master Firebase 시크릿 주입 후 실기기 푸시 스모크 (N4-1/3/4)
|
||||||
- 오프라인 메시지 큐 / 멀티디바이스 실시간 설정 동기화 고도화
|
- 오프라인 메시지 큐 / 멀티디바이스 실시간 설정 동기화 고도화
|
||||||
- 온보딩 말투 UX 디테일 (PoC #1 결과는 맨 마지막에 반영)
|
- 온보딩 말투 UX 디테일 (PoC #1 결과는 맨 마지막에 반영)
|
||||||
- iOS 빌드 (v1 범위 밖)
|
- iOS 빌드 (v1 범위 밖)
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,6 @@ key.properties
|
||||||
**/*.jks
|
**/*.jks
|
||||||
*.jks
|
*.jks
|
||||||
*.keystore
|
*.keystore
|
||||||
|
|
||||||
|
# Firebase — never commit real project credentials (see docs/fcm-setup.md).
|
||||||
|
app/google-services.json
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,11 @@ plugins {
|
||||||
id("dev.flutter.flutter-gradle-plugin")
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply only when Master drops google-services.json (see docs/fcm-setup.md).
|
||||||
|
if (file("google-services.json").exists()) {
|
||||||
|
apply(plugin = "com.google.gms.google-services")
|
||||||
|
}
|
||||||
|
|
||||||
val keystoreProperties = Properties()
|
val keystoreProperties = Properties()
|
||||||
val keystorePropertiesFile = rootProject.file("key.properties")
|
val keystorePropertiesFile = rootProject.file("key.properties")
|
||||||
val hasReleaseKeystore = keystorePropertiesFile.exists()
|
val hasReleaseKeystore = keystorePropertiesFile.exists()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
{
|
||||||
|
"project_info": {
|
||||||
|
"project_number": "REPLACE_WITH_FIREBASE_PROJECT_NUMBER",
|
||||||
|
"project_id": "ykavu-REPLACE",
|
||||||
|
"storage_bucket": "ykavu-REPLACE.appspot.com"
|
||||||
|
},
|
||||||
|
"client": [
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:REPLACE:android:REPLACE",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.ykavu.ykavu_mobile"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "REPLACE_WITH_ANDROID_API_KEY"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration_version": "1"
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
<application
|
<application
|
||||||
android:label="와카뷰"
|
android:label="와카뷰"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ plugins {
|
||||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
id("com.android.application") version "8.7.3" apply false
|
id("com.android.application") version "8.7.3" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
||||||
|
id("com.google.gms.google-services") version "4.4.2" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
include(":app")
|
include(":app")
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ class DataFlowScreen extends StatelessWidget {
|
||||||
body: '초안이 필요할 때: 최근 대화 몇 줄 + 말투 샘플 일부(최소 컨텍스트)\n'
|
body: '초안이 필요할 때: 최근 대화 몇 줄 + 말투 샘플 일부(최소 컨텍스트)\n'
|
||||||
'채팅 릴레이: 보낸 메시지 본문\n'
|
'채팅 릴레이: 보낸 메시지 본문\n'
|
||||||
'계정: 표시 이름·초대 코드·세션 토큰\n'
|
'계정: 표시 이름·초대 코드·세션 토큰\n'
|
||||||
'푸시(준비 중): 디바이스 토큰만 등록, 실제 전송은 FCM 프로젝트 연결 후',
|
'푸시: Firebase 연결 시 실 FCM 토큰 등록 · 미연결 시 install 플레이스홀더 (docs/fcm-setup.md)',
|
||||||
),
|
),
|
||||||
_section(
|
_section(
|
||||||
context,
|
context,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
/// Resolves a device push token for core-backend registration.
|
||||||
|
///
|
||||||
|
/// Prefer a real FCM registration token when Firebase is configured
|
||||||
|
/// (`google-services.json` on Android). Otherwise fall back to a stable
|
||||||
|
/// `install:` placeholder that the server intentionally skips for delivery.
|
||||||
|
class PushTokenService {
|
||||||
|
PushTokenService({this.storeInstallId, this.readInstallId});
|
||||||
|
|
||||||
|
/// Persist a newly generated install id (KV / secure storage).
|
||||||
|
final Future<void> Function(String installId)? storeInstallId;
|
||||||
|
|
||||||
|
/// Load a previously stored install id.
|
||||||
|
final Future<String?> Function()? readInstallId;
|
||||||
|
|
||||||
|
Future<({String token, String platform, bool isFcm})> resolve() async {
|
||||||
|
final platform = _platformLabel();
|
||||||
|
final fcm = await _tryFirebaseToken();
|
||||||
|
if (fcm != null && fcm.isNotEmpty) {
|
||||||
|
return (token: fcm, platform: platform, isFcm: true);
|
||||||
|
}
|
||||||
|
final installId = await _ensureInstallId();
|
||||||
|
return (token: 'install:$installId', platform: platform, isFcm: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> _tryFirebaseToken() async {
|
||||||
|
// Web push needs a separate Firebase web config + VAPID; skip until provided.
|
||||||
|
if (kIsWeb) return null;
|
||||||
|
if (defaultTargetPlatform != TargetPlatform.android &&
|
||||||
|
defaultTargetPlatform != TargetPlatform.iOS) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (Firebase.apps.isEmpty) {
|
||||||
|
await Firebase.initializeApp();
|
||||||
|
}
|
||||||
|
final messaging = FirebaseMessaging.instance;
|
||||||
|
await messaging.requestPermission(alert: true, badge: true, sound: true);
|
||||||
|
final token = await messaging.getToken();
|
||||||
|
if (token == null || token.isEmpty) return null;
|
||||||
|
return token;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('FCM token unavailable (using install placeholder): $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> _ensureInstallId() async {
|
||||||
|
final existing = await readInstallId?.call();
|
||||||
|
if (existing != null && existing.isNotEmpty) return existing;
|
||||||
|
final rand = Random.secure();
|
||||||
|
final installId =
|
||||||
|
List.generate(16, (_) => rand.nextInt(256).toRadixString(16).padLeft(2, '0')).join();
|
||||||
|
await storeInstallId?.call(installId);
|
||||||
|
return installId;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _platformLabel() {
|
||||||
|
if (kIsWeb) return 'web';
|
||||||
|
if (defaultTargetPlatform == TargetPlatform.android) return 'android';
|
||||||
|
if (defaultTargetPlatform == TargetPlatform.iOS) return 'ios';
|
||||||
|
return 'other';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
@ -7,6 +6,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../db/app_database.dart';
|
import '../db/app_database.dart';
|
||||||
import '../models/models.dart';
|
import '../models/models.dart';
|
||||||
import '../services/api_client.dart';
|
import '../services/api_client.dart';
|
||||||
|
import '../services/push_token_service.dart';
|
||||||
|
|
||||||
class SessionState extends ChangeNotifier {
|
class SessionState extends ChangeNotifier {
|
||||||
SessionState({ApiClient? api, AppDatabase? db})
|
SessionState({ApiClient? api, AppDatabase? db})
|
||||||
|
|
@ -148,22 +148,25 @@ class SessionState extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers a stable install id as the push token until Firebase Messaging
|
/// Registers a real FCM token when Firebase is configured; otherwise a stable
|
||||||
/// is wired with a real FCM registration token (roadmap B).
|
/// `install:` placeholder (server skips placeholders for delivery).
|
||||||
Future<void> _registerDeviceTokenBestEffort() async {
|
Future<void> _registerDeviceTokenBestEffort() async {
|
||||||
if (user == null) return;
|
if (user == null) return;
|
||||||
try {
|
try {
|
||||||
_db ??= await _openDb();
|
_db ??= await _openDb();
|
||||||
var installId = await _db!.getKv(_kDeviceInstallId);
|
final resolved = await PushTokenService(
|
||||||
if (installId == null || installId.isEmpty) {
|
readInstallId: () => _db!.getKv(_kDeviceInstallId),
|
||||||
final rand = Random.secure();
|
storeInstallId: (id) => _db!.setKv(_kDeviceInstallId, id),
|
||||||
installId = List.generate(16, (_) => rand.nextInt(256).toRadixString(16).padLeft(2, '0')).join();
|
).resolve();
|
||||||
await _db!.setKv(_kDeviceInstallId, installId);
|
|
||||||
}
|
|
||||||
await _api.registerDeviceToken(
|
await _api.registerDeviceToken(
|
||||||
userId: user!.id,
|
userId: user!.id,
|
||||||
token: 'install:$installId',
|
token: resolved.token,
|
||||||
platform: defaultTargetPlatform == TargetPlatform.android ? 'android' : 'other',
|
platform: resolved.platform,
|
||||||
|
);
|
||||||
|
debugPrint(
|
||||||
|
resolved.isFcm
|
||||||
|
? 'device token registered (FCM)'
|
||||||
|
: 'device token registered (install placeholder)',
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('device token register skipped: $e');
|
debugPrint('device token register skipped: $e');
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "88.0.0"
|
version: "88.0.0"
|
||||||
|
_flutterfire_internals:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _flutterfire_internals
|
||||||
|
sha256: "460e9e684edb461d85498fc166ff8416f303f22216838d302d80676b348c6a4c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.75"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -93,10 +101,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: characters
|
name: characters
|
||||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.0"
|
||||||
charcode:
|
charcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -209,6 +217,54 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
version: "7.0.1"
|
||||||
|
firebase_core:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: firebase_core
|
||||||
|
sha256: "6f22d1c62e0c20976f02cd842c7b7cd3c0f561cc2052586411871045c08860c9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.12.1"
|
||||||
|
firebase_core_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_core_platform_interface
|
||||||
|
sha256: f74d1d6fabccf7743b0144c2ed363d81049e258e22428ebb6fb0faec2fa7d938
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.0.0"
|
||||||
|
firebase_core_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_core_web
|
||||||
|
sha256: ddab99d709b8c27dd47576eb05a1e719e07a2aa45c009a49ae92ac4d2a8ca555
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.9.1"
|
||||||
|
firebase_messaging:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: firebase_messaging
|
||||||
|
sha256: "30ad2d59bcd86117dc49d278c8998a0fb390c5a3202f6e43e4bd215d3f1d0556"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "16.4.3"
|
||||||
|
firebase_messaging_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_messaging_platform_interface
|
||||||
|
sha256: "4d144cb42b9a5a42855596be2d7682d32c50169f42e7df2cb3278ecf935e7d63"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.9.2"
|
||||||
|
firebase_messaging_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: firebase_messaging_web
|
||||||
|
sha256: fcd25d0b9da55766ef4d28ae05a7460f5189635d6b42607adcd9f08818fd35f0
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.2.3"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -356,26 +412,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.0.2"
|
version: "10.0.9"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.10"
|
version: "3.0.9"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_testing
|
name: leak_tracker_testing
|
||||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.1"
|
||||||
lints:
|
lints:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -396,26 +452,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: matcher
|
name: matcher
|
||||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.19"
|
version: "0.12.17"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.0"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.18.0"
|
version: "1.16.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -713,10 +769,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.11"
|
version: "0.7.4"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -729,10 +785,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vector_math
|
name: vector_math
|
||||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.1.4"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -798,5 +854,5 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.10.0-0 <4.0.0"
|
dart: ">=3.8.1 <4.0.0"
|
||||||
flutter: ">=3.29.0"
|
flutter: ">=3.29.0"
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,8 @@ dependencies:
|
||||||
flutter_secure_storage: ^10.3.1
|
flutter_secure_storage: ^10.3.1
|
||||||
sqlcipher_flutter_libs: ^0.6.8
|
sqlcipher_flutter_libs: ^0.6.8
|
||||||
google_fonts: ^6.3.2
|
google_fonts: ^6.3.2
|
||||||
|
firebase_core: ^4.12.1
|
||||||
|
firebase_messaging: ^16.4.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue