feat(messaging): Web 알림 API 기준을 추가한다
Flutter Web foreground 알림을 준비하기 위해 초기화 옵션과 token prefix 경계를 public API로 고정한다. 포트/환경 기준 문서와 로드맵 정리, agent-task 리뷰 산출물도 함께 반영한다.
This commit is contained in:
parent
871c792128
commit
caafd4f320
17 changed files with 1140 additions and 21 deletions
15
README.md
15
README.md
|
|
@ -50,6 +50,21 @@ cp .env.example .env
|
|||
docker compose up
|
||||
```
|
||||
|
||||
## 워크스페이스 포트 기준
|
||||
|
||||
Nexo가 소유한 runtime 기본값은 core HTTP `18065`, push-proxy HTTP
|
||||
`18066`이다. 두 포트는 외부 소비자가 있을 수 있는 compatibility
|
||||
baseline이므로 즉시 바꾸지 않는다. Flutter/web preview가 필요해지는
|
||||
후속 작업에서는 frontend slot `13040`을 우선 후보로 쓰고, backend
|
||||
포트 이동이 필요하면 `18040` 계열 후보를 별도 migration plan에서
|
||||
다룬다.
|
||||
|
||||
Mattermost upstream 개발 compose가 쓰는 DB/cache/object/search/mail/metrics
|
||||
포트는 upstream dev auxiliary로 취급한다. Nexo compose는 기본적으로
|
||||
Postgres를 host에 publish하지 않고 compose network 내부 `db:5432`로만
|
||||
사용한다. 상세 inventory와 secret boundary는
|
||||
`services/core/compose/README.md`의 Port And Environment Inventory를 본다.
|
||||
|
||||
## 주요 명령
|
||||
|
||||
| 목적 | 명령 | 비고 |
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ Nexo의 upstream-followable Mattermost runtime, push-proxy, Flutter test host, w
|
|||
|
||||
## 상태
|
||||
|
||||
[진행중]
|
||||
[완료]
|
||||
|
||||
## 승격 조건
|
||||
|
||||
|
|
@ -35,20 +35,23 @@ Nexo의 upstream-followable Mattermost runtime, push-proxy, Flutter test host, w
|
|||
|
||||
Nexo가 소유한 runtime 포트와 upstream 개발 보조 포트를 분리해 정리한다.
|
||||
|
||||
- [ ] [runtime-map] core HTTP, push-proxy, Flutter test host, web preview 포트를 host publish와 container/internal 기준으로 나눠 정리한다.
|
||||
- [ ] [upstream-map] upstream Mattermost dev compose의 DB/cache/object/search/mail/metrics/dashboard 포트를 nexo-owned runtime과 구분해 충돌 후보를 표시한다.
|
||||
- [ ] [slot-plan] Nexo 기본 후보를 frontend `13040`, backend `18065/18066` 유지, compose-internal DB/cache 우선으로 문서화하고 변경 필요 시 `18040` 계열 후보를 남긴다.
|
||||
- [ ] [secret-boundary] Firebase, APNs, Mattermost token, private host 값은 tracked 문서에 쓰지 않고 evidence 위치와 책임 경계만 남긴다.
|
||||
- [x] [runtime-map] core HTTP, push-proxy, Flutter test host, web preview 포트를 host publish와 container/internal 기준으로 나눠 정리한다.
|
||||
- [x] [upstream-map] upstream Mattermost dev compose의 DB/cache/object/search/mail/metrics/dashboard 포트를 nexo-owned runtime과 구분해 충돌 후보를 표시한다.
|
||||
- [x] [slot-plan] Nexo 기본 후보를 frontend `13040`, backend `18065/18066` 유지, compose-internal DB/cache 우선으로 문서화하고 변경 필요 시 `18040` 계열 후보를 남긴다.
|
||||
- [x] [secret-boundary] Firebase, APNs, Mattermost token, private host 값은 tracked 문서에 쓰지 않고 evidence 위치와 책임 경계만 남긴다.
|
||||
|
||||
## 완료 리뷰
|
||||
|
||||
- 상태: 없음
|
||||
- 요청일: 없음
|
||||
- 완료 근거: 없음
|
||||
- 상태: 승인됨
|
||||
- 요청일: 2026-06-07
|
||||
- 완료 근거:
|
||||
- `services/core/compose/README.md`에 nexo runtime, upstream dev auxiliary, slot plan, tracked value boundary inventory를 추가했다.
|
||||
- `README.md`, `apps/flutter-test/README.md`, `docs/runtime-image-validation.md`가 같은 포트/환경 기준을 가리키도록 정리했다.
|
||||
- 검증: `git diff --check`, `rg --sort path -n '(^## Port And Environment Inventory|^### Nexo Runtime|^### Upstream Dev Services|^### Slot Plan|^### Tracked Value Boundary|워크스페이스 포트 기준|frontend slot `13040`|source of truth|현재 포트)' README.md services/core/compose/README.md apps/flutter-test/README.md docs/runtime-image-validation.md`
|
||||
- 리뷰 필요:
|
||||
- [ ] 사용자가 완료 결과를 확인했다
|
||||
- [ ] archive 이동을 승인했다
|
||||
- 리뷰 코멘트: 없음
|
||||
- [x] 사용자가 완료 결과를 확인했다
|
||||
- [x] archive 이동을 승인했다
|
||||
- 리뷰 코멘트: 사용자 요청으로 마일스톤을 종료하고 다음 마일스톤으로 이동한다.
|
||||
|
||||
## 범위 제외
|
||||
|
||||
|
|
@ -34,10 +34,10 @@ Mattermost server/webapp/push-proxy는 upstream-followable runtime으로 두고,
|
|||
- [완료] Linux 알림 선행 정리
|
||||
- 경로: `agent-roadmap/archive/phase/messaging-runtime/milestones/linux-notification-preflight.md`
|
||||
- 요약: Linux 배포판/desktop environment 결정 없이 plugin 등록, notification daemon 후보, bridge 계약, fixture, evidence 기준을 먼저 정리했다.
|
||||
- [진행중] 워크스페이스 포트/환경 표준화
|
||||
- 경로: `agent-roadmap/phase/messaging-runtime/milestones/workspace-port-env-standardization.md`
|
||||
- 요약: Mattermost core, push-proxy, Flutter test/web surface, upstream dev services 포트를 workspace 공통 대역과 remote runtime 문서 기준으로 정리한다.
|
||||
- [계획] Flutter Web 알림
|
||||
- [완료] 워크스페이스 포트/환경 표준화
|
||||
- 경로: `agent-roadmap/archive/phase/messaging-runtime/milestones/workspace-port-env-standardization.md`
|
||||
- 요약: Mattermost core, push-proxy, Flutter test/web surface, upstream dev services 포트를 workspace 공통 대역과 remote runtime 문서 기준으로 정리했다.
|
||||
- [진행중] Flutter Web 알림
|
||||
- 경로: `agent-roadmap/phase/messaging-runtime/milestones/flutter-web-notification.md`
|
||||
- 요약: `packages/messaging_flutter`를 Flutter Web/Chrome foreground browser notification까지 확장하고, Full Web Push는 별도 후속 계약으로 분리한다.
|
||||
- [계획] iOS 알림 테스트
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
## 상태
|
||||
|
||||
[계획]
|
||||
[진행중]
|
||||
|
||||
## 승격 조건
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,194 @@
|
|||
<!-- task=m-flutter-web-notification/01_api_options plan=0 tag=WEBAPI -->
|
||||
|
||||
# Code Review Reference - WEBAPI
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`.
|
||||
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation.
|
||||
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-06-07
|
||||
task=m-flutter-web-notification/01_api_options, plan=0, tag=WEBAPI
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/messaging-runtime/milestones/flutter-web-notification.md`
|
||||
- Task ids:
|
||||
- `api-options`: 기존 `initialize()` 호출을 깨지 않는 옵션 모델을 추가하고, web notification permission/display 정책과 token prefix 경계를 문서화한다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [WEBAPI-1] Initialize Options API | [x] |
|
||||
| [WEBAPI-2] Web Token Boundary Documentation | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] `NexoMessagingInitializeOptions`와 token prefix boundary 상수를 public Dart API로 추가한다.
|
||||
- [x] `initialize()`가 기존 no-arg 호출을 유지하면서 옵션으로 FCM permission/token work를 제어하게 한다.
|
||||
- [x] README에 web foreground와 Full Web Push token prefix 경계를 문서화한다.
|
||||
- [x] options와 token prefix 단위 테스트를 추가하고 기존 token refresh 테스트를 유지한다.
|
||||
- [x] `cd packages/messaging_flutter && flutter test`와 `cd packages/messaging_flutter && flutter analyze --no-fatal-infos`를 실행한다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
|
||||
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G04_N.log`로 아카이브한다.
|
||||
- [x] active `PLAN-*-G??.md`를 `plan_local_G04_M.log`로 아카이브한다.
|
||||
- [x] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [x] PASS이면 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-flutter-web-notification/01_api_options/`로 이동한다.
|
||||
- [x] PASS이면 task group이 `m-flutter-web-notification`임을 런타임 완료 이벤트 메타데이터에 보고한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
계획과 동일한 방식으로 구현했다. 추가적인 변경 없음.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
1. **`_kDeviceTokenPrefix` 제거**: 기존 `const String _kDeviceTokenPrefix = 'android_rn'`를 제거하고 `_saveDeviceToken()`에서 `NexoMessagingTokenPrefix.androidV2` 직접 사용으로 변경했다. 결과적 토큰 포맷(`android_rn-v2:...`)은 동일하게 유지된다.
|
||||
|
||||
2. **`_requestPermission()` 분리**: 기존 `_requestPermission()`가 permission request와 token read를 함께 수행했지만,PLAN에 따라 permission request만 담당하게 하고 token read를 새 private `_saveCurrentDeviceToken()`으로 분리했다.
|
||||
|
||||
3. **`initialize()` 시그니처**: 기본 인수를 사용하여 기존 no-arg 호출 (`instance.initialize()`)이 계속 컴파일되도록 보장했다.
|
||||
|
||||
4. **`NexoMessagingTokenPrefix` 공개 상수 클래스**: Android, iOS, Web 3개 플랫폼의 토큰 prefix를 상수 클래스로 공개 문서화했다. `webV2`는 미래 Full Web Push용 reserved prefix이다.
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 아래 `검증 결과` 참조
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `initialize()` no-arg call site가 계속 컴파일되는지 확인한다.
|
||||
- Android token formatting이 `android_rn-v2:`로 유지되는지 확인한다.
|
||||
- Web `web_rn-v2:`는 reserved boundary일 뿐 server registration 구현으로 오해되지 않는지 확인한다.
|
||||
- `Roadmap Targets`가 `api-options` 하나만 완료 대상으로 삼는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
### WEBAPI-1 중간 검증
|
||||
|
||||
```text
|
||||
$ cd packages/messaging_flutter && flutter test
|
||||
00:00 +0: loading /config/workspace/nexo/packages/messaging_flutter/test/nexo_messaging_test.dart
|
||||
00:00 +0: NotificationOpenedEvent.fromMap parses server_url, channel_id, root_id, is_crt_enabled
|
||||
00:00 +1: NotificationOpenedEvent.fromMap defaults isCRTEnabled to false when flag is absent or non-true
|
||||
00:00 +2: NotificationOpenedEvent.fromMap accepts boolean and numeric-string CRT truthy flags
|
||||
00:00 +3: NexoMessagingPlugin.handleNativeEvent opened event emits NotificationOpenedEvent on stream
|
||||
[PushNotification] Opened: channelId=channel-123 rootId=null
|
||||
00:00 +4: NexoMessagingPlugin.handleNativeEvent message event with userInteraction routes through opened handler
|
||||
[PushNotification] Opened: channelId=channel-xyz rootId=null
|
||||
00:00 +5: NexoMessagingPlugin.handleNativeEvent message event without userInteraction does not trigger navigation
|
||||
00:00 +6: NexoMessagingPlugin.handleNativeEvent opened event with CRT and rootId routes to thread callback
|
||||
[PushNotification] Opened: channelId=channel-123 rootId=root-789
|
||||
00:00 +7: NexoMessagingPlugin.handleNativeEvent opened event with boolean CRT and rootId routes to thread callback
|
||||
[PushNotification] Opened: channelId=channel-123 rootId=root-789
|
||||
00:00 +8: NexoMessagingPlugin.handleNativeEvent opened event with CRT but empty rootId falls back to channel callback
|
||||
[PushNotification] Opened: channelId=channel-123 rootId=
|
||||
00:00 +9: NexoMessagingPlugin.handleNativeEvent opened event without server_url skips navigation callbacks
|
||||
[PushNotification] Opened: channelId=channel-123 rootId=null
|
||||
00:00 +10: NexoMessagingPlugin.handleNativeEvent every event is forwarded to onNotification stream
|
||||
[PushNotification] Clear: channelId=channel-123
|
||||
00:00 +11: NexoMessagingPlugin.handleNativeEvent token_refresh event saves formatted device token and invokes callback
|
||||
[PushNotification] Device token saved
|
||||
00:00 +12: NexoMessagingPlugin.handleNativeEvent debugSendNativeEventForTesting invokes native debug method
|
||||
00:00 +13: Channel constants notification and action channel names are preserved
|
||||
00:00 +14: NexoMessagingInitializeOptions has all default values set to true
|
||||
00:00 +15: NexoMessagingInitializeOptions can override all fields to false
|
||||
00:00 +16: NexoMessagingInitializeOptions can override individual fields
|
||||
00:00 +17: NexoMessagingTokenPrefix androidV2 has the expected value
|
||||
00:00 +18: NexoMessagingTokenPrefix appleV2 has the expected value
|
||||
00:00 +19: NexoMessagingTokenPrefix webV2 has the expected value
|
||||
00:00 +20: All tests passed!
|
||||
```
|
||||
|
||||
### WEBAPI-2 중간 검증
|
||||
|
||||
```text
|
||||
$ cd packages/messaging_flutter && flutter analyze --no-fatal-infos
|
||||
Analyzing messaging_flutter...
|
||||
No issues found! (ran in 2.1s)
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```text
|
||||
$ cd packages/messaging_flutter && flutter test
|
||||
00:00 +0: loading /config/workspace/nexo/packages/messaging_flutter/test/nexo_messaging_test.dart
|
||||
00:00 +0: NotificationOpenedEvent.fromMap parses server_url, channel_id, root_id, is_crt_enabled
|
||||
00:00 +1: NotificationOpenedEvent.fromMap defaults isCRTEnabled to false when flag is absent or non-true
|
||||
00:00 +2: NotificationOpenedEvent.fromMap accepts boolean and numeric-string CRT truthy flags
|
||||
00:00 +3: NexoMessagingPlugin.handleNativeEvent opened event emits NotificationOpenedEvent on stream
|
||||
[PushNotification] Opened: channelId=channel-123 rootId=null
|
||||
00:00 +4: NexoMessagingPlugin.handleNativeEvent message event with userInteraction routes through opened handler
|
||||
[PushNotification] Opened: channelId=channel-xyz rootId=null
|
||||
00:00 +5: NexoMessagingPlugin.handleNativeEvent message event without userInteraction does not trigger navigation
|
||||
00:00 +6: NexoMessagingPlugin.handleNativeEvent opened event with CRT and rootId routes to thread callback
|
||||
[PushNotification] Opened: channelId=channel-123 rootId=root-789
|
||||
00:00 +7: NexoMessagingPlugin.handleNativeEvent opened event with boolean CRT and rootId routes to thread callback
|
||||
[PushNotification] Opened: channelId=channel-123 rootId=root-789
|
||||
00:00 +8: NexoMessagingPlugin.handleNativeEvent opened event with CRT but empty rootId falls back to channel callback
|
||||
[PushNotification] Opened: channelId=channel-123 rootId=
|
||||
00:00 +9: NexoMessagingPlugin.handleNativeEvent opened event without server_url skips navigation callbacks
|
||||
[PushNotification] Opened: channelId=channel-123 rootId=null
|
||||
00:00 +10: NexoMessagingPlugin.handleNativeEvent every event is forwarded to onNotification stream
|
||||
[PushNotification] Clear: channelId=channel-123
|
||||
00:00 +11: NexoMessagingPlugin.handleNativeEvent token_refresh event saves formatted device token and invokes callback
|
||||
[PushNotification] Device token saved
|
||||
00:00 +12: NexoMessagingPlugin.handleNativeEvent debugSendNativeEventForTesting invokes native debug method
|
||||
00:00 +13: Channel constants notification and action channel names are preserved
|
||||
00:00 +14: NexoMessagingInitializeOptions has all default values set to true
|
||||
00:00 +15: NexoMessagingInitializeOptions can override all fields to false
|
||||
00:00 +16: NexoMessagingInitializeOptions can override individual fields
|
||||
00:00 +17: NexoMessagingTokenPrefix androidV2 has the expected value
|
||||
00:00 +18: NexoMessagingTokenPrefix appleV2 has the expected value
|
||||
00:00 +19: NexoMessagingTokenPrefix webV2 has the expected value
|
||||
00:00 +20: All tests passed!
|
||||
|
||||
$ cd packages/messaging_flutter && flutter analyze --no-fatal-infos
|
||||
Analyzing messaging_flutter...
|
||||
No issues found! (ran in 2.1s)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
- 종합 판정: PASS
|
||||
- 차원별 평가:
|
||||
- correctness: Pass
|
||||
- completeness: Pass
|
||||
- test coverage: Pass
|
||||
- API contract: Pass
|
||||
- code quality: Pass
|
||||
- plan deviation: Pass
|
||||
- verification trust: Pass
|
||||
- 발견된 문제: 없음
|
||||
- 다음 단계: PASS이므로 active plan/review 파일을 로그로 아카이브하고 `complete.log` 작성 후 task directory를 archive로 이동한다.
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# Complete - m-flutter-web-notification/01_api_options
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-06-07
|
||||
|
||||
## 요약
|
||||
|
||||
Flutter Web 알림 선행 API 옵션 작업을 1회 리뷰 루프로 검토했고 최종 판정은 PASS다.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_local_G04_0.log` | `code_review_local_G04_0.log` | PASS | `NexoMessagingInitializeOptions`, token prefix boundary, README 문서화, 단위 테스트와 analyze 검증이 계획 범위와 일치한다. |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- `NexoMessagingInitializeOptions`와 `NexoMessagingTokenPrefix`를 public API로 추가했다.
|
||||
- `initialize()`가 기존 no-arg 호출을 유지하면서 token refresh, permission request, current token registration을 옵션으로 제어하게 했다.
|
||||
- Web foreground notification과 future Full Web Push token prefix boundary를 README에 문서화했다.
|
||||
- options/token prefix 단위 테스트를 추가하고 기존 `android_rn-v2:` token refresh 테스트를 유지했다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `cd packages/messaging_flutter && flutter test` - PASS; 20개 테스트가 통과했다.
|
||||
- `cd packages/messaging_flutter && flutter analyze --no-fatal-infos` - PASS; `No issues found!`.
|
||||
|
||||
## Roadmap Completion
|
||||
|
||||
- Milestone: `agent-roadmap/phase/messaging-runtime/milestones/flutter-web-notification.md`
|
||||
- Completed task ids:
|
||||
- `api-options`: PASS; evidence=`agent-task/archive/2026/06/m-flutter-web-notification/01_api_options/plan_local_G04_0.log`, `agent-task/archive/2026/06/m-flutter-web-notification/01_api_options/code_review_local_G04_0.log`; verification=`cd packages/messaging_flutter && flutter test`, `cd packages/messaging_flutter && flutter analyze --no-fatal-infos`
|
||||
- Not completed task ids: 없음
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
<!-- task=m-flutter-web-notification/01_api_options plan=0 tag=WEBAPI -->
|
||||
|
||||
# Plan - WEBAPI
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
`CODE_REVIEW-local-G04.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수다. 구현 후 검증을 실행하고 active 파일을 그대로 둔 채 리뷰 준비를 보고한다. 구현 중 사용자만 결정할 수 있는 선택, 사용자 소유 외부 환경/secret, 또는 범위 충돌이 있으면 `사용자 리뷰 요청` 섹션을 근거와 함께 채우고 멈춘다. 사용자에게 직접 질문하거나 선택지를 제시하거나 `USER_REVIEW.md`, `complete.log`, archive log를 만들지 않는다.
|
||||
|
||||
## 배경
|
||||
|
||||
현재 `initialize()`는 인자 없이 Android/FCM permission과 token registration을 항상 수행한다. Flutter Web foreground notification을 추가하려면 기존 호출을 깨지 않으면서 플랫폼별 permission/token 정책을 분리할 옵션 모델이 먼저 필요하다. 이 작업은 첫 Epic의 API 기반 작업이며, 실제 web plugin 등록과 `package:web` interop는 후속 split 작업에서 처리한다.
|
||||
|
||||
## 사용자 리뷰 요청 흐름
|
||||
|
||||
구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 프롬프트는 금지되며, code-review가 요청 정당성을 검증하고 필요할 때만 `USER_REVIEW.md`를 작성한다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/messaging-runtime/milestones/flutter-web-notification.md`
|
||||
- Task ids:
|
||||
- `api-options`: 기존 `initialize()` 호출을 깨지 않는 옵션 모델을 추가하고, web notification permission/display 정책과 token prefix 경계를 문서화한다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-roadmap/current.md`
|
||||
- `agent-roadmap/phase/messaging-runtime/milestones/flutter-web-notification.md`
|
||||
- `agent-test/local/rules.md`
|
||||
- `agent-test/local/messaging-flutter-smoke.md`
|
||||
- `agent-test/local/client-app-smoke.md`
|
||||
- `packages/messaging_flutter/pubspec.yaml`
|
||||
- `packages/messaging_flutter/lib/nexo_messaging.dart`
|
||||
- `packages/messaging_flutter/lib/src/nexo_messaging_plugin.dart`
|
||||
- `packages/messaging_flutter/lib/src/notification_opened_event.dart`
|
||||
- `packages/messaging_flutter/lib/src/push_notification_type.dart`
|
||||
- `packages/messaging_flutter/test/nexo_messaging_test.dart`
|
||||
- `packages/messaging_flutter/README.md`
|
||||
- `packages/messaging_flutter/analysis_options.yaml`
|
||||
- `apps/flutter-test/lib/main.dart`
|
||||
- `apps/flutter-test/test/widget_test.dart`
|
||||
- `apps/flutter-test/pubspec.yaml`
|
||||
- `apps/flutter-test/analysis_options.yaml`
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
- `test_env=local`.
|
||||
- `agent-test/local/rules.md`를 읽었다.
|
||||
- `packages/messaging_flutter/**` 변경이므로 `agent-test/local/messaging-flutter-smoke.md`를 읽었다.
|
||||
- 앱 호출부 영향 확인을 위해 `agent-test/local/client-app-smoke.md`도 읽었다.
|
||||
- 적용 명령: `cd packages/messaging_flutter && flutter test`, `cd packages/messaging_flutter && flutter analyze --no-fatal-infos`.
|
||||
- `flutter`와 `dart`는 `/sdk/flutter/bin/` 아래에서 확인했다.
|
||||
- 외부 Firebase, device, runtime credential은 이 작업 검증에 필요하지 않다.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- 새 옵션 value object와 token prefix 상수는 기존 테스트가 없다. 단위 테스트를 추가한다.
|
||||
- `initialize()`가 no-arg 호출을 유지하는지 컴파일과 analyze로 확인한다.
|
||||
- 실제 web permission/display 동작은 이 plan의 범위가 아니며 `02+01_web_plugin_baseline`과 후속 foreground Epic에서 다룬다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- renamed/removed symbol: none.
|
||||
- 변경될 call site: `apps/flutter-test/lib/main.dart`의 `plugin.initialize()` no-arg 호출은 유지되어야 한다.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
- split decision policy를 plan 파일 선택 전에 평가했다.
|
||||
- 첫 Epic은 public API/options와 web platform implementation이 자연스러운 dependency boundary를 가진다.
|
||||
- shared task group: `m-flutter-web-notification`.
|
||||
- sibling subtasks:
|
||||
- `01_api_options`: 독립 선행 작업. 의존 없음.
|
||||
- `02+01_web_plugin_baseline`: `01_api_options`의 PASS와 `complete.log`가 선행되어야 한다.
|
||||
- 이 plan은 `api-options`만 완료 대상으로 삼는다. `plugin-web`과 `interop`는 의존 subtask에서 처리한다.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- 포함: Dart public options model, initialize 옵션 wiring, token prefix boundary constants/docs, package unit tests.
|
||||
- 제외: `pubspec.yaml` web platform entry, `package:web` dependency, web implementation file, browser Notification API wrapper, Flutter test web scaffold.
|
||||
- 제외 이유: web registration/interop는 dependency and platform boundary가 있어 `02+01_web_plugin_baseline`에서 별도 리뷰한다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
- `local-G04`: public API가 추가되지만 범위가 Dart package 내부와 README/test로 제한되고 검증 명령이 결정적이다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `NexoMessagingInitializeOptions`와 token prefix boundary 상수를 public Dart API로 추가한다.
|
||||
- [ ] `initialize()`가 기존 no-arg 호출을 유지하면서 옵션으로 FCM permission/token work를 제어하게 한다.
|
||||
- [ ] README에 web foreground와 Full Web Push token prefix 경계를 문서화한다.
|
||||
- [ ] options와 token prefix 단위 테스트를 추가하고 기존 token refresh 테스트를 유지한다.
|
||||
- [ ] `cd packages/messaging_flutter && flutter test`와 `cd packages/messaging_flutter && flutter analyze --no-fatal-infos`를 실행한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## [WEBAPI-1] Initialize Options API
|
||||
|
||||
### 문제
|
||||
|
||||
[packages/messaging_flutter/lib/src/nexo_messaging_plugin.dart](/config/workspace/nexo/packages/messaging_flutter/lib/src/nexo_messaging_plugin.dart:48)의 `initialize()`가 항상 native channel, FCM token refresh, permission request, token read를 한 번에 실행한다. Web foreground notification은 server device token registration과 별도이므로 옵션 모델이 필요하다.
|
||||
|
||||
Before:
|
||||
|
||||
```dart
|
||||
48 Future<void> initialize() async {
|
||||
49 _listenNativeChannel();
|
||||
50 _listenFcmTokenRefresh();
|
||||
51 await _requestPermission();
|
||||
52 }
|
||||
```
|
||||
|
||||
### 해결 방법
|
||||
|
||||
새 파일 `packages/messaging_flutter/lib/src/nexo_messaging_options.dart`를 만들고 public export에 추가한다. 기본값은 기존 동작을 보존한다.
|
||||
|
||||
After:
|
||||
|
||||
```dart
|
||||
class NexoMessagingInitializeOptions {
|
||||
const NexoMessagingInitializeOptions({
|
||||
this.listenForTokenRefresh = true,
|
||||
this.requestNotificationPermission = true,
|
||||
this.registerDeviceToken = true,
|
||||
});
|
||||
|
||||
final bool listenForTokenRefresh;
|
||||
final bool requestNotificationPermission;
|
||||
final bool registerDeviceToken;
|
||||
}
|
||||
|
||||
class NexoMessagingTokenPrefix {
|
||||
static const String androidV2 = 'android_rn-v2';
|
||||
static const String appleV2 = 'apple_rn-v2';
|
||||
static const String webV2 = 'web_rn-v2';
|
||||
}
|
||||
```
|
||||
|
||||
`initialize()`는 아래 형태로 바꾼다.
|
||||
|
||||
```dart
|
||||
Future<void> initialize({
|
||||
NexoMessagingInitializeOptions options =
|
||||
const NexoMessagingInitializeOptions(),
|
||||
}) async {
|
||||
_listenNativeChannel();
|
||||
if (options.listenForTokenRefresh) {
|
||||
_listenFcmTokenRefresh();
|
||||
}
|
||||
if (options.requestNotificationPermission) {
|
||||
await _requestPermission();
|
||||
}
|
||||
if (options.registerDeviceToken) {
|
||||
await _saveCurrentDeviceToken();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`_requestPermission()`은 permission request만 담당하게 하고, 기존 token read는 새 private `_saveCurrentDeviceToken()`로 분리한다.
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `packages/messaging_flutter/lib/src/nexo_messaging_options.dart` 추가
|
||||
- [ ] `packages/messaging_flutter/lib/nexo_messaging.dart`에 options export 추가
|
||||
- [ ] `packages/messaging_flutter/lib/src/nexo_messaging_plugin.dart`에 options import와 `initialize()` 옵션 wiring 추가
|
||||
- [ ] `_saveDeviceToken()`이 `NexoMessagingTokenPrefix.androidV2`를 사용하도록 변경
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
- `packages/messaging_flutter/test/nexo_messaging_test.dart`에 `NexoMessagingInitializeOptions` 기본값과 override 테스트를 추가한다.
|
||||
- `NexoMessagingTokenPrefix` 상수 값 테스트를 추가한다.
|
||||
- 기존 `token_refresh event saves formatted device token` 테스트가 `android_rn-v2:`를 계속 검증하도록 유지한다.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```sh
|
||||
cd packages/messaging_flutter && flutter test
|
||||
```
|
||||
|
||||
기대 결과: 모든 Dart unit test가 통과한다.
|
||||
|
||||
## [WEBAPI-2] Web Token Boundary Documentation
|
||||
|
||||
### 문제
|
||||
|
||||
[packages/messaging_flutter/README.md](/config/workspace/nexo/packages/messaging_flutter/README.md:192)는 Android/iOS token format만 말하고, [packages/messaging_flutter/README.md](/config/workspace/nexo/packages/messaging_flutter/README.md:228)의 token flow도 Web foreground와 Full Web Push의 경계를 설명하지 않는다.
|
||||
|
||||
Before:
|
||||
|
||||
```md
|
||||
195 NexoMessagingPlugin.instance.onDeviceTokenReady = (String deviceToken) {
|
||||
196 // Android implemented format: android_rn-v2:YOUR_FCM_TOKEN
|
||||
197 // iOS preflight server format: apple_rn-v2:YOUR_APNS_TOKEN
|
||||
198 // Send this token to the nexo server.
|
||||
199 };
|
||||
```
|
||||
|
||||
### 해결 방법
|
||||
|
||||
README의 Platform Status, Host Integration Contract, Dart Usage, Token And Credential Flow, Public API Contract에 다음 경계를 추가한다.
|
||||
|
||||
- Web foreground browser notification은 앱이 열린 상태의 display/open routing API이며 server device-token registration을 만들지 않는다.
|
||||
- `web_rn-v2:`는 Full Web Push 후속 Milestone에서 service worker/VAPID/core/push-proxy 계약이 생길 때 사용할 reserved prefix다.
|
||||
- `initialize()`는 no-arg 호출을 유지하되, 옵션으로 permission/token behavior를 제어할 수 있다.
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `packages/messaging_flutter/README.md` Platform Status에 Web planned/baseline 설명 추가
|
||||
- [ ] Dart Usage에 `initialize(options: ...)` 예시 추가
|
||||
- [ ] Token And Credential Flow에 `web_rn-v2:` reserved boundary 추가
|
||||
- [ ] Public API Contract에 `NexoMessagingInitializeOptions`와 `NexoMessagingTokenPrefix` 추가
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
- 문서 변경은 unit test 대상이 아니다. API 테스트는 `WEBAPI-1`에서 작성한다.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```sh
|
||||
cd packages/messaging_flutter && flutter analyze --no-fatal-infos
|
||||
```
|
||||
|
||||
기대 결과: 새 public API와 docs snippets가 analyzer issue 없이 유지된다.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
| --- | --- |
|
||||
| `packages/messaging_flutter/lib/src/nexo_messaging_options.dart` | WEBAPI-1 |
|
||||
| `packages/messaging_flutter/lib/nexo_messaging.dart` | WEBAPI-1 |
|
||||
| `packages/messaging_flutter/lib/src/nexo_messaging_plugin.dart` | WEBAPI-1 |
|
||||
| `packages/messaging_flutter/test/nexo_messaging_test.dart` | WEBAPI-1 |
|
||||
| `packages/messaging_flutter/README.md` | WEBAPI-2 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```sh
|
||||
cd packages/messaging_flutter && flutter test
|
||||
cd packages/messaging_flutter && flutter analyze --no-fatal-infos
|
||||
```
|
||||
|
||||
기대 결과: 두 명령 모두 exit code `0`.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
<!-- task=m-flutter-web-notification/02+01_web_plugin_baseline plan=0 tag=WEBPLUGIN -->
|
||||
|
||||
# Code Review Reference - WEBPLUGIN
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Do not start until predecessor `01_api_options` has `complete.log`.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place.
|
||||
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation.
|
||||
> Finalization is review-agent-only.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-06-07
|
||||
task=m-flutter-web-notification/02+01_web_plugin_baseline, plan=0, tag=WEBPLUGIN
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/messaging-runtime/milestones/flutter-web-notification.md`
|
||||
- Task ids:
|
||||
- `plugin-web`: `nexo_messaging`에 Flutter Web plugin 등록과 web 전용 구현 파일을 추가한다.
|
||||
- `interop`: browser `Notification` API 연동은 `package:web` 기반으로 작성하고, Android/iOS/macOS import와 분리한다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [WEBPLUGIN-1] Flutter Web Plugin Registration | [ ] |
|
||||
| [WEBPLUGIN-2] Package Web Interop Boundary | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] 구현 시작 전 `01_api_options` predecessor `complete.log`가 active 또는 archive 경로에 있는지 확인한다.
|
||||
- [ ] `packages/messaging_flutter/pubspec.yaml`에 `web: ^1.1.1` dependency와 Flutter web platform entry를 추가한다.
|
||||
- [ ] `NexoMessagingWeb.registerWith()` web 전용 파일을 추가하고 공용 Dart 파일이 `package:web`를 직접 import하지 않게 유지한다.
|
||||
- [ ] browser `Notification` API 접근은 `package:web` 기반 web-only interop wrapper로 분리한다.
|
||||
- [ ] web baseline unit/analyze 검증을 추가 또는 보강한다.
|
||||
- [ ] `cd packages/messaging_flutter && flutter pub get`, `cd packages/messaging_flutter && flutter test`, `cd packages/messaging_flutter && flutter analyze --no-fatal-infos`를 실행한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
|
||||
- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [ ] predecessor `01_api_options` complete.log가 실제로 있었는지 확인한다.
|
||||
- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G06_N.log`로 아카이브한다.
|
||||
- [ ] active `PLAN-*-G??.md`를 `plan_local_G06_M.log`로 아카이브한다.
|
||||
- [ ] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [ ] PASS이면 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-flutter-web-notification/02+01_web_plugin_baseline/`로 이동한다.
|
||||
- [ ] PASS이면 task group이 `m-flutter-web-notification`임을 런타임 완료 이벤트 메타데이터에 보고한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다._
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `pubspec.yaml` web platform entry가 Flutter docs schema와 맞는지 확인한다.
|
||||
- `package:web` import가 web-only 파일로 제한되는지 확인한다.
|
||||
- `plugin-web`과 `interop`만 Roadmap completion 대상으로 기록되는지 확인한다.
|
||||
- Foreground permission/display/click behavior가 이 plan에서 과하게 구현되지 않았는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
### WEBPLUGIN-1 중간 검증
|
||||
|
||||
```text
|
||||
$ cd packages/messaging_flutter && flutter pub get
|
||||
(output)
|
||||
|
||||
$ cd packages/messaging_flutter && flutter analyze --no-fatal-infos
|
||||
(output)
|
||||
```
|
||||
|
||||
### WEBPLUGIN-2 중간 검증
|
||||
|
||||
```text
|
||||
$ cd packages/messaging_flutter && flutter analyze --no-fatal-infos
|
||||
(output)
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```text
|
||||
$ cd packages/messaging_flutter && flutter pub get
|
||||
(output)
|
||||
|
||||
$ cd packages/messaging_flutter && flutter test
|
||||
(output)
|
||||
|
||||
$ cd packages/messaging_flutter && flutter analyze --no-fatal-infos
|
||||
(output)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
|
||||
|
|
@ -0,0 +1,257 @@
|
|||
<!-- task=m-flutter-web-notification/02+01_web_plugin_baseline plan=0 tag=WEBPLUGIN -->
|
||||
|
||||
# Plan - WEBPLUGIN
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
이 task는 `agent-task/m-flutter-web-notification/01_api_options/complete.log`가 생긴 뒤 구현한다. `CODE_REVIEW-local-G06.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채워야 완료다. 사용자에게 직접 질문하지 말고, 차단이 있으면 review stub의 `사용자 리뷰 요청`에 근거를 남긴다.
|
||||
|
||||
## 배경
|
||||
|
||||
Flutter Web plugin 등록과 browser Notification interop는 public initialize/options boundary가 먼저 있어야 한다. 이 plan은 첫 Epic의 `plugin-web`과 `interop`를 묶어 처리하되, foreground permission/display public API는 다음 Epic으로 남긴다.
|
||||
|
||||
## 사용자 리뷰 요청 흐름
|
||||
|
||||
구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 프롬프트는 금지되며, code-review가 요청 정당성을 검증하고 필요할 때만 `USER_REVIEW.md`를 작성한다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/messaging-runtime/milestones/flutter-web-notification.md`
|
||||
- Task ids:
|
||||
- `plugin-web`: `nexo_messaging`에 Flutter Web plugin 등록과 web 전용 구현 파일을 추가한다.
|
||||
- `interop`: browser `Notification` API 연동은 `package:web` 기반으로 작성하고, Android/iOS/macOS import와 분리한다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-roadmap/current.md`
|
||||
- `agent-roadmap/phase/messaging-runtime/milestones/flutter-web-notification.md`
|
||||
- `agent-test/local/rules.md`
|
||||
- `agent-test/local/messaging-flutter-smoke.md`
|
||||
- `agent-test/local/client-app-smoke.md`
|
||||
- `packages/messaging_flutter/pubspec.yaml`
|
||||
- `packages/messaging_flutter/lib/nexo_messaging.dart`
|
||||
- `packages/messaging_flutter/lib/src/nexo_messaging_plugin.dart`
|
||||
- `packages/messaging_flutter/lib/src/notification_opened_event.dart`
|
||||
- `packages/messaging_flutter/lib/src/push_notification_type.dart`
|
||||
- `packages/messaging_flutter/test/nexo_messaging_test.dart`
|
||||
- `packages/messaging_flutter/README.md`
|
||||
- `packages/messaging_flutter/analysis_options.yaml`
|
||||
- `apps/flutter-test/lib/main.dart`
|
||||
- `apps/flutter-test/test/widget_test.dart`
|
||||
- `apps/flutter-test/pubspec.yaml`
|
||||
- `apps/flutter-test/analysis_options.yaml`
|
||||
- Official Flutter docs: `https://docs.flutter.dev/packages-and-plugins/developing-packages`
|
||||
- Official `package:web` page/API: `https://pub.dev/packages/web`, `https://pub.dev/documentation/web/latest/web/Notification-extension-type.html`
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
- `test_env=local`.
|
||||
- `agent-test/local/rules.md`, `messaging-flutter-smoke.md`, `client-app-smoke.md`를 읽었다.
|
||||
- 적용 명령: `cd packages/messaging_flutter && flutter pub get`, `cd packages/messaging_flutter && flutter test`, `cd packages/messaging_flutter && flutter analyze --no-fatal-infos`.
|
||||
- Web plugin registration이 consuming app dependency graph에 영향을 주므로 필요 시 `cd apps/flutter-test && flutter pub get`도 실행한다.
|
||||
- Flutter SDK와 Dart SDK는 `/sdk/flutter/bin/`에서 확인했다.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- 기존 tests는 web plugin registration과 browser interop를 검증하지 않는다.
|
||||
- 이 plan은 web implementation file의 registerWith와 internal interop mapper를 Dart unit/analyze 대상으로 만든다.
|
||||
- 실제 Chrome permission prompt/display/click smoke는 `foreground`와 `smoke` Epic에서 별도 처리한다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- renamed/removed symbol: none.
|
||||
- 새 dependency/call site:
|
||||
- `packages/messaging_flutter/pubspec.yaml`에 `web: ^1.1.1` 추가.
|
||||
- `flutter.plugin.platforms.web`에 `pluginClass`와 `fileName` 추가.
|
||||
- 새 web 전용 파일이 `package:web`를 import한다. Android/iOS/macOS 공용 파일은 `package:web`를 직접 import하지 않는다.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
- split decision policy를 plan 파일 선택 전에 평가했다.
|
||||
- shared task group: `m-flutter-web-notification`.
|
||||
- 이 subtask directory `02+01_web_plugin_baseline`은 predecessor index `01`에 의존한다.
|
||||
- predecessor status: `agent-task/m-flutter-web-notification/01_api_options/complete.log`는 아직 없다. 구현 시작 전 해당 active sibling 또는 archive candidate complete.log를 확인해야 한다.
|
||||
- 이 plan은 `plugin-web`과 `interop`를 완료 대상으로 삼고, `permission`, `display`, `event-map`은 후속 Epic으로 남긴다.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- 포함: Flutter web platform registration, web implementation entry file, `package:web` dependency, web-only browser Notification interop boundary.
|
||||
- 제외: permission request public API, browser notification display/click routing behavior, service worker, VAPID, `web_rn-v2` server registration, core/push-proxy platform changes, Flutter test web UI.
|
||||
- 제외 이유: 첫 Epic baseline은 platform support를 깨지 않게 올리는 기준선이고, user-visible foreground behavior는 다음 Epic에서 검증 단위가 다르다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
- `local-G06`: package manifest, generated plugin registration contract, web-only imports, and dependency resolution risk가 있지만 범위가 package 내부로 제한되고 deterministic Flutter commands로 검증 가능하다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] 구현 시작 전 `01_api_options` predecessor `complete.log`가 active 또는 archive 경로에 있는지 확인한다.
|
||||
- [ ] `packages/messaging_flutter/pubspec.yaml`에 `web: ^1.1.1` dependency와 Flutter web platform entry를 추가한다.
|
||||
- [ ] `NexoMessagingWeb.registerWith()` web 전용 파일을 추가하고 공용 Dart 파일이 `package:web`를 직접 import하지 않게 유지한다.
|
||||
- [ ] browser `Notification` API 접근은 `package:web` 기반 web-only interop wrapper로 분리한다.
|
||||
- [ ] web baseline unit/analyze 검증을 추가 또는 보강한다.
|
||||
- [ ] `cd packages/messaging_flutter && flutter pub get`, `cd packages/messaging_flutter && flutter test`, `cd packages/messaging_flutter && flutter analyze --no-fatal-infos`를 실행한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
1. `agent-task/m-flutter-web-notification/01_api_options/complete.log` 또는 archive의 matching `01_api_options/complete.log`를 확인한다.
|
||||
2. predecessor가 없으면 구현하지 말고 review stub의 `사용자 리뷰 요청`이 아니라 계획 미충족으로 보고한다.
|
||||
3. predecessor가 있으면 이 plan의 구현을 진행한다.
|
||||
|
||||
## [WEBPLUGIN-1] Flutter Web Plugin Registration
|
||||
|
||||
### 문제
|
||||
|
||||
[packages/messaging_flutter/pubspec.yaml](/config/workspace/nexo/packages/messaging_flutter/pubspec.yaml:21)은 Android/iOS/macOS만 등록되어 있어 Flutter Web app이 plugin registrant를 생성할 수 없다. Flutter 공식 문서는 web plugin Dart implementation에 `pluginClass`와 `fileName`을 사용한다고 설명한다.
|
||||
|
||||
Before:
|
||||
|
||||
```yaml
|
||||
21 flutter:
|
||||
22 plugin:
|
||||
23 platforms:
|
||||
24 android:
|
||||
25 package: com.tokilabs.nexo.messaging
|
||||
26 pluginClass: NexoMessagingPlugin
|
||||
27 ios:
|
||||
28 pluginClass: NexoMessagingPlugin
|
||||
29 macos:
|
||||
30 pluginClass: NexoMessagingPlugin
|
||||
```
|
||||
|
||||
### 해결 방법
|
||||
|
||||
`pubspec.yaml`에 web dependency와 platform entry를 추가한다.
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
firebase_messaging: ^15.2.5
|
||||
web: ^1.1.1
|
||||
|
||||
flutter:
|
||||
plugin:
|
||||
platforms:
|
||||
web:
|
||||
pluginClass: NexoMessagingWeb
|
||||
fileName: src/nexo_messaging_web.dart
|
||||
```
|
||||
|
||||
`lib/src/nexo_messaging_web.dart`를 추가한다.
|
||||
|
||||
```dart
|
||||
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
|
||||
|
||||
class NexoMessagingWeb {
|
||||
static void registerWith(Registrar registrar) {
|
||||
// Baseline registration; foreground behavior is wired in later tasks.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `packages/messaging_flutter/pubspec.yaml` dependency/platform entry 추가
|
||||
- [ ] `packages/messaging_flutter/lib/src/nexo_messaging_web.dart` 추가
|
||||
- [ ] `packages/messaging_flutter/pubspec.lock` 갱신 여부 확인. package plugin lockfile은 이 repo에서 ignored이므로 source diff에 남기지 않는다.
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
- unit test가 web registrant generation 자체를 실행하지는 않는다.
|
||||
- `flutter pub get`과 `flutter analyze --no-fatal-infos`로 pubspec schema/import correctness를 검증한다.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```sh
|
||||
cd packages/messaging_flutter && flutter pub get
|
||||
cd packages/messaging_flutter && flutter analyze --no-fatal-infos
|
||||
```
|
||||
|
||||
기대 결과: dependency resolution과 analyzer가 통과한다.
|
||||
|
||||
## [WEBPLUGIN-2] Package Web Interop Boundary
|
||||
|
||||
### 문제
|
||||
|
||||
[packages/messaging_flutter/lib/src/nexo_messaging_plugin.dart](/config/workspace/nexo/packages/messaging_flutter/lib/src/nexo_messaging_plugin.dart:3)는 공용 Dart API에서 `firebase_messaging`를 직접 import하고 있고, browser `Notification` API 경계가 없다. `package:web`는 Dart web interop의 장기 해법이며, `Notification.permission`과 `Notification.requestPermission()` API를 제공한다.
|
||||
|
||||
### 해결 방법
|
||||
|
||||
`package:web` import는 web-only implementation 아래로 제한한다. 예시 구조:
|
||||
|
||||
```text
|
||||
lib/src/web/browser_notification_interop.dart
|
||||
lib/src/web/browser_notification_interop_web.dart
|
||||
```
|
||||
|
||||
`browser_notification_interop_web.dart`만 아래 import를 가진다.
|
||||
|
||||
```dart
|
||||
import 'dart:js_interop';
|
||||
import 'package:web/web.dart' as web;
|
||||
```
|
||||
|
||||
첫 Epic에서는 public permission/display API를 만들지 않는다. 대신 future foreground task가 사용할 private wrapper만 둔다.
|
||||
|
||||
```dart
|
||||
class BrowserNotificationInterop {
|
||||
const BrowserNotificationInterop();
|
||||
|
||||
String get permission => web.Notification.permission.toString();
|
||||
|
||||
Future<String> requestPermission() async {
|
||||
final result = await web.Notification.requestPermission().toDart;
|
||||
return result.toDart;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
실제 display/click wiring은 `permission`, `display`, `event-map` Task에서 구현한다.
|
||||
|
||||
### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `packages/messaging_flutter/lib/src/web/browser_notification_interop.dart` 추가
|
||||
- [ ] `packages/messaging_flutter/lib/src/web/browser_notification_interop_web.dart` 추가
|
||||
- [ ] non-web common API가 `package:web`를 직접 import하지 않는지 확인
|
||||
- [ ] README Platform Status 또는 Current Limitations에 Web baseline registration 상태를 반영
|
||||
|
||||
### 테스트 작성
|
||||
|
||||
- wrapper가 web-only인 경우 VM unit test에서 직접 import하지 않는다.
|
||||
- analyzer로 import boundary를 검증한다.
|
||||
- 가능하면 web implementation file의 non-browser pure mapper만 별도 testable helper로 분리한다. 실제 Notification prompt/display는 이 plan의 테스트 대상이 아니다.
|
||||
|
||||
### 중간 검증
|
||||
|
||||
```sh
|
||||
cd packages/messaging_flutter && flutter analyze --no-fatal-infos
|
||||
```
|
||||
|
||||
기대 결과: non-web analyzer에서도 `package:web` import가 공용 VM test path를 깨지 않는다.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
| --- | --- |
|
||||
| `packages/messaging_flutter/pubspec.yaml` | WEBPLUGIN-1 |
|
||||
| `packages/messaging_flutter/lib/src/nexo_messaging_web.dart` | WEBPLUGIN-1 |
|
||||
| `packages/messaging_flutter/lib/src/web/browser_notification_interop.dart` | WEBPLUGIN-2 |
|
||||
| `packages/messaging_flutter/lib/src/web/browser_notification_interop_web.dart` | WEBPLUGIN-2 |
|
||||
| `packages/messaging_flutter/README.md` | WEBPLUGIN-2 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```sh
|
||||
cd packages/messaging_flutter && flutter pub get
|
||||
cd packages/messaging_flutter && flutter test
|
||||
cd packages/messaging_flutter && flutter analyze --no-fatal-infos
|
||||
```
|
||||
|
||||
기대 결과: 세 명령 모두 exit code `0`.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -68,6 +68,11 @@ Run on a connected device or emulator:
|
|||
flutter run
|
||||
```
|
||||
|
||||
This harness does not own a default host HTTP port. Android/iOS/device smoke
|
||||
tests should receive runtime URLs from the invoking shell or ignored local
|
||||
profiles. If a browser preview is added later, use the workspace frontend slot
|
||||
`13040` unless a later migration plan changes the baseline.
|
||||
|
||||
For Android FCM smoke testing, make sure the Android app has the required
|
||||
Firebase configuration, including `android/app/google-services.json`.
|
||||
Use the plugin-owned
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
# Nexo Runtime Image 검증
|
||||
|
||||
이 문서는 nexo-owned runtime image의 registry, tag, architecture, verification digest를 기록한다.
|
||||
현재 포트와 환경 기준의 원천은
|
||||
`../services/core/compose/README.md`의 Port And Environment Inventory다. 이
|
||||
문서의 명령 출력은 당시 검증 증거이며, 새 private host, token,
|
||||
credential, concrete endpoint 값을 추가 기록하지 않는다.
|
||||
|
||||
## 게시된 Image 요약
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ copying upstream mobile code.
|
|||
| iOS | Stub | Plugin scaffold exists, behavior is not implemented yet |
|
||||
| macOS | Stub | Plugin scaffold exists; user-provided signing, machine, APNs/Firebase scope, and evidence handoff requirements are documented in [`../../docs/macos-notification-test-guide.md`](../../docs/macos-notification-test-guide.md) |
|
||||
| Windows | Not registered | No Flutter plugin scaffold yet; user-provided Windows runner, packaging, identity, signing, and evidence handoff requirements are documented in [`../../docs/windows-notification-test-guide.md`](../../docs/windows-notification-test-guide.md) |
|
||||
| Web | Planned | Browser foreground notification (display/open routing while tab is active) is the first web milestone. Full Web Push (service worker, VAPID, server device-token registration) follows. API baseline (`NexoMessagingInitializeOptions`, token prefix boundaries) is available in this version. |
|
||||
|
||||
## Repository Layout
|
||||
|
||||
|
|
@ -153,6 +154,23 @@ Initialize the plugin:
|
|||
await NexoMessagingPlugin.instance.initialize();
|
||||
```
|
||||
|
||||
The plugin initializes with default options that match the previous behaviour
|
||||
(subscribes to native events, listens for FCM token refreshes, requests
|
||||
notification permission, and registers the device token).
|
||||
|
||||
To control which initialisation steps run, pass
|
||||
`NexoMessagingInitializeOptions`:
|
||||
|
||||
```dart
|
||||
await NexoMessagingPlugin.instance.initialize(
|
||||
options: const NexoMessagingInitializeOptions(
|
||||
listenForTokenRefresh: true,
|
||||
requestNotificationPermission: true,
|
||||
registerDeviceToken: true,
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
Listen to raw notification events:
|
||||
|
||||
```dart
|
||||
|
|
@ -237,6 +255,8 @@ native storage and native push operations that need those values.
|
|||
APNs push-proxy registration. An iOS FCM registration token is useful for
|
||||
Firebase direct-send smoke, but it is not the server registration token unless
|
||||
a future server/push-proxy path explicitly supports FCM-backed iOS delivery.
|
||||
Web (Full Push) reserves `web_rn-v2:<raw-push-token>` for service-worker
|
||||
registration; see the boundary note below.
|
||||
4. The host app sends the formatted device token to the nexo server. Native
|
||||
signature verification uses the stored device token and compares the raw FCM
|
||||
token portion with signed push claims.
|
||||
|
|
@ -258,6 +278,27 @@ when exactly one server is known. Host apps should still register device tokens
|
|||
with the server after `onDeviceTokenReady` and should not parse secret values or
|
||||
raw native storage details from README examples.
|
||||
|
||||
## Web Token Boundary
|
||||
|
||||
Web support is delivered in two milestones. Understanding the boundary between
|
||||
them is important for server registration and token-format expectations.
|
||||
|
||||
**Browser foreground notification** (the first web milestone) is a display/open
|
||||
routing API for when the tab is active. It does **not** create a server device-
|
||||
token registration because the browser already knows how to reach the client
|
||||
through the open connection.
|
||||
|
||||
**Full Web Push** (a future milestone) will introduce service-worker handling,
|
||||
VAPID keys, and a `core/push-proxy` contract. That milestone will use the
|
||||
reserved token prefix `web_rn-v2:<raw-push-token>` for server device-token
|
||||
registration. Until that milestone lands:
|
||||
|
||||
- `NexoMessagingTokenPrefix.webV2` is a documentation-only constant.
|
||||
- No web implementation files, `package:web` dependency, or browser
|
||||
`Notification.requestPermission()` / `Notification.showNotification()` calls
|
||||
are present in this package.
|
||||
- Those are tracked under the subsequent `02+01_web_plugin_baseline` task.
|
||||
|
||||
## Public API Contract
|
||||
|
||||
Apps should import only `package:nexo_messaging/nexo_messaging.dart`. The
|
||||
|
|
@ -278,6 +319,8 @@ they are listed here.
|
|||
| `NotificationOpenedEvent` | Keep | Stable Dart value object for opened notification routing fields. |
|
||||
| `PushNotificationType` | Compatibility | Native event string constants. Future typed event wrappers should reduce direct app branching on these constants. |
|
||||
| `notificationChannelName`, `actionChannelName`, `listenNativeChannelForTesting()`, `debugSendNativeEventForTesting()`, `handleNativeEvent()` | Testing-only | Exposed for repository-owned tests through `@visibleForTesting`; consuming apps should not depend on them. |
|
||||
| `NexoMessagingInitializeOptions` | New | Options class for `initialize()`. Default values preserve backward-compatible behaviour. |
|
||||
| `NexoMessagingTokenPrefix` | New | Static constants (`androidV2`, `appleV2`, `webV2`) documenting the token-prefix format for each platform target. |
|
||||
|
||||
Deprecated candidates are compatibility-only app dependencies on raw
|
||||
`onNotification` maps, direct branching on `PushNotificationType`, and parsing
|
||||
|
|
@ -609,5 +652,8 @@ Evidence to capture:
|
|||
|
||||
- Android is the only implemented runtime target.
|
||||
- iOS and macOS are scaffolded but no-op.
|
||||
- Web is planned: API baseline (`NexoMessagingInitializeOptions`, token prefix
|
||||
constants) is ready; browser foreground notification and Full Web Push
|
||||
implementation are tracked in subsequent milestones.
|
||||
- Full FCM delivery cannot be reliably automated in a headless test runner.
|
||||
- The `apps/flutter-test/` app should stay focused on package integration behavior.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export 'src/nexo_messaging_options.dart';
|
||||
export 'src/nexo_messaging_plugin.dart';
|
||||
export 'src/notification_opened_event.dart';
|
||||
export 'src/push_notification_type.dart';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
/// Options controlling which platform-specific initialization tasks
|
||||
/// [NexoMessagingPlugin.initialize()] performs.
|
||||
///
|
||||
/// Default values preserve the previous behaviour where [initialize()] always
|
||||
/// listens for FCM token refreshes, requests notification permission, and
|
||||
/// registers the device token.
|
||||
class NexoMessagingInitializeOptions {
|
||||
const NexoMessagingInitializeOptions({
|
||||
this.listenForTokenRefresh = true,
|
||||
this.requestNotificationPermission = true,
|
||||
this.registerDeviceToken = true,
|
||||
});
|
||||
|
||||
/// Whether to subscribe to FCM token refresh events.
|
||||
///
|
||||
/// When `false` the plugin will not call [_listenFcmTokenRefresh].
|
||||
final bool listenForTokenRefresh;
|
||||
|
||||
/// Whether to request notification permissions from the platform.
|
||||
///
|
||||
/// When `false` the plugin will not call [_requestPermission].
|
||||
final bool requestNotificationPermission;
|
||||
|
||||
/// Whether to read the current device token and emit it through
|
||||
/// [NexoMessagingPlugin.onDeviceTokenReady] / native storage.
|
||||
///
|
||||
/// When `false` the plugin will not call [_saveCurrentDeviceToken].
|
||||
final bool registerDeviceToken;
|
||||
}
|
||||
|
||||
/// Token-prefix boundaries for different platform targets.
|
||||
///
|
||||
/// These constants document the format the SDK uses (or will use) when
|
||||
/// forwarding device tokens to the host app via [NexoMessagingPlugin.onDeviceTokenReady]
|
||||
/// or native storage.
|
||||
///
|
||||
/// - `androidV2` is the currently implemented prefix for Android FCM tokens.
|
||||
/// - `appleV2` is reserved for future iOS APNs/FCM server registration.
|
||||
/// - `webV2` is reserved for Full Web Push (service-worker / VAPID) in a
|
||||
/// future milestone. **Web foreground notification** (browser display/Open
|
||||
/// routing while the tab is active) does NOT create a server device-token
|
||||
/// registration and therefore does not use this prefix.
|
||||
///
|
||||
/// Example: `android_rn-v2:YOUR_FCM_TOKEN`
|
||||
class NexoMessagingTokenPrefix {
|
||||
static const String androidV2 = 'android_rn-v2';
|
||||
static const String appleV2 = 'apple_rn-v2';
|
||||
static const String webV2 = 'web_rn-v2';
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import 'package:firebase_messaging/firebase_messaging.dart';
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'nexo_messaging_options.dart';
|
||||
import 'notification_opened_event.dart';
|
||||
import 'push_notification_type.dart';
|
||||
|
||||
|
|
@ -11,7 +12,6 @@ const String _kNotificationChannelName =
|
|||
'com.tokilabs.nexo.messaging/notifications';
|
||||
const String _kActionChannelName =
|
||||
'com.tokilabs.nexo.messaging/notification_actions';
|
||||
const String _kDeviceTokenPrefix = 'android_rn';
|
||||
|
||||
class NexoMessagingPlugin {
|
||||
NexoMessagingPlugin._();
|
||||
|
|
@ -45,10 +45,27 @@ class NexoMessagingPlugin {
|
|||
void Function(String serverUrl, String rootId)? onNavigateToThread;
|
||||
void Function(String deviceToken)? onDeviceTokenReady;
|
||||
|
||||
Future<void> initialize() async {
|
||||
/// Initialize the messaging plugin.
|
||||
///
|
||||
/// By default ([options] unchanged) the plugin subscribes to native events,
|
||||
/// listens for FCM token refreshes, requests notification permission, and
|
||||
/// registers the device token — matching the pre-Version-0.0.2 behaviour.
|
||||
///
|
||||
/// Pass [NexoMessagingInitializeOptions] to disable any of those steps.
|
||||
Future<void> initialize({
|
||||
NexoMessagingInitializeOptions options =
|
||||
const NexoMessagingInitializeOptions(),
|
||||
}) async {
|
||||
_listenNativeChannel();
|
||||
_listenFcmTokenRefresh();
|
||||
await _requestPermission();
|
||||
if (options.listenForTokenRefresh) {
|
||||
_listenFcmTokenRefresh();
|
||||
}
|
||||
if (options.requestNotificationPermission) {
|
||||
await _requestPermission();
|
||||
}
|
||||
if (options.registerDeviceToken) {
|
||||
await _saveCurrentDeviceToken();
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
|
|
@ -95,7 +112,11 @@ class NexoMessagingPlugin {
|
|||
debugPrint(
|
||||
'[PushNotification] Permission: ${settings.authorizationStatus}',
|
||||
);
|
||||
}
|
||||
|
||||
/// Reads the current FCM/APNs token and saves it via [_saveDeviceToken].
|
||||
/// Called during [initialize] when [NexoMessagingInitializeOptions.registerDeviceToken] is `true`.
|
||||
Future<void> _saveCurrentDeviceToken() async {
|
||||
final token = await FirebaseMessaging.instance.getToken();
|
||||
if (token != null) await _saveDeviceToken(token);
|
||||
}
|
||||
|
|
@ -208,7 +229,7 @@ class NexoMessagingPlugin {
|
|||
|
||||
Future<void> _saveDeviceToken(String token) async {
|
||||
try {
|
||||
final formattedToken = '$_kDeviceTokenPrefix-v2:$token';
|
||||
final formattedToken = '${NexoMessagingTokenPrefix.androidV2}:$token';
|
||||
await _actionChannel.invokeMethod('saveDeviceToken', {
|
||||
'token': formattedToken,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -287,4 +287,55 @@ void main() {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('NexoMessagingInitializeOptions', () {
|
||||
test('has all default values set to true', () {
|
||||
const options = NexoMessagingInitializeOptions();
|
||||
expect(options.listenForTokenRefresh, isTrue);
|
||||
expect(options.requestNotificationPermission, isTrue);
|
||||
expect(options.registerDeviceToken, isTrue);
|
||||
});
|
||||
|
||||
test('can override all fields to false', () {
|
||||
const options = NexoMessagingInitializeOptions(
|
||||
listenForTokenRefresh: false,
|
||||
requestNotificationPermission: false,
|
||||
registerDeviceToken: false,
|
||||
);
|
||||
expect(options.listenForTokenRefresh, isFalse);
|
||||
expect(options.requestNotificationPermission, isFalse);
|
||||
expect(options.registerDeviceToken, isFalse);
|
||||
});
|
||||
|
||||
test('can override individual fields', () {
|
||||
const options1 = NexoMessagingInitializeOptions(
|
||||
requestNotificationPermission: false,
|
||||
);
|
||||
expect(options1.listenForTokenRefresh, isTrue);
|
||||
expect(options1.requestNotificationPermission, isFalse);
|
||||
expect(options1.registerDeviceToken, isTrue);
|
||||
|
||||
const options2 = NexoMessagingInitializeOptions(
|
||||
listenForTokenRefresh: false,
|
||||
registerDeviceToken: false,
|
||||
);
|
||||
expect(options2.listenForTokenRefresh, isFalse);
|
||||
expect(options2.requestNotificationPermission, isTrue);
|
||||
expect(options2.registerDeviceToken, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('NexoMessagingTokenPrefix', () {
|
||||
test('androidV2 has the expected value', () {
|
||||
expect(NexoMessagingTokenPrefix.androidV2, 'android_rn-v2');
|
||||
});
|
||||
|
||||
test('appleV2 has the expected value', () {
|
||||
expect(NexoMessagingTokenPrefix.appleV2, 'apple_rn-v2');
|
||||
});
|
||||
|
||||
test('webV2 has the expected value', () {
|
||||
expect(NexoMessagingTokenPrefix.webV2, 'web_rn-v2');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,70 @@ test deployment can run beside the existing Mattermost service.
|
|||
The Docker network subnet is fixed to `172.38.0.0/16`. Treat this range as
|
||||
reserved for nexo and do not reuse it for other Docker networks.
|
||||
|
||||
## Port And Environment Inventory
|
||||
|
||||
This compose file is the source of truth for the nexo-owned runtime port shape.
|
||||
It keeps public development defaults stable, while private hostnames,
|
||||
credentials, tokens, and concrete remote endpoints stay in ignored environment
|
||||
profiles or private run logs.
|
||||
|
||||
### Nexo Runtime
|
||||
|
||||
| Surface | Host publish | Container or internal target | Notes |
|
||||
| --- | --- | --- | --- |
|
||||
| Core HTTP and webapp | `${NEXO_CORE_PORT:-18065}` | `core:8065` | Compatibility baseline. Keep as-is unless a migration plan updates consumers. |
|
||||
| Push-proxy HTTP | `${NEXO_PUSH_PROXY_PORT:-18066}` | `push-proxy:8066` | Host publish is for smoke and diagnostics. Core talks to `http://push-proxy:8066` on the compose network. |
|
||||
| Postgres | none | `db:5432` | Compose-internal only. Do not publish by default. |
|
||||
| Flutter test host | none | device/emulator app process | Runtime URL values belong in ignored local profiles or the invoking shell. |
|
||||
| Flutter web preview | `13040` candidate | future web host | Reserved frontend slot for a future web preview; this repository currently does not publish a Flutter web host by default. |
|
||||
|
||||
### Upstream Dev Services
|
||||
|
||||
The Mattermost upstream development compose files under
|
||||
`services/core/server/` publish several common local ports. Treat them as
|
||||
upstream dev auxiliaries, not nexo runtime ports.
|
||||
|
||||
| Upstream service | Typical host publish | Role |
|
||||
| --- | --- | --- |
|
||||
| Mattermost dev proxy/server | `8065` | Upstream server/webapp development entrypoint |
|
||||
| PostgreSQL | `5432` | Upstream dev database |
|
||||
| Redis | `6379` | Upstream cache/session auxiliary |
|
||||
| MinIO / console | `9000`, `9002` | Upstream object storage and console |
|
||||
| Azurite | `10000` | Upstream Azure storage emulator |
|
||||
| Inbucket web / SMTP / POP3 | `9001`, `10025`, `10110` | Upstream mail testing |
|
||||
| OpenLDAP | `389`, `636` | Upstream LDAP testing |
|
||||
| Elasticsearch / transport | `9200`, `9300` | Upstream search testing |
|
||||
| OpenSearch | `9201` | Upstream search testing |
|
||||
| DejaVu | `1358` | Upstream search dashboard |
|
||||
| Keycloak | `8484 -> 8080` | Upstream SAML/OIDC testing |
|
||||
| Prometheus | `9090` | Upstream metrics testing |
|
||||
| Grafana | `3000` | Upstream dashboard testing |
|
||||
| Loki | `3100` | Upstream log aggregation testing |
|
||||
| OTel collector | `13133` | Upstream telemetry collector health |
|
||||
|
||||
If these upstream ports conflict with local services, change the upstream dev
|
||||
compose invocation or environment for that run. Do not move nexo runtime
|
||||
defaults just to satisfy an upstream dev auxiliary conflict.
|
||||
|
||||
### Slot Plan
|
||||
|
||||
- Frontend preview slot: reserve `13040` for a future Flutter/web preview.
|
||||
- Backend compatibility slots: keep core `18065` and push-proxy `18066`.
|
||||
- Compose-internal auxiliaries: keep DB/cache/object storage private to the
|
||||
compose network unless a separate plan requires host access.
|
||||
- Future backend migration candidate: if `18065`/`18066` must move, use a
|
||||
planned `18040` range migration with compatibility notes and consumer smoke
|
||||
evidence instead of changing defaults in place.
|
||||
|
||||
### Tracked Value Boundary
|
||||
|
||||
Tracked files may name environment variable names, example paths, sanitized
|
||||
profile labels, and non-secret public development defaults. Do not commit raw
|
||||
Firebase or APNs files, Mattermost tokens, passwords, private hostnames,
|
||||
private ports, or concrete remote endpoint URLs. Store those values in ignored
|
||||
`.env` files, ignored runtime data, or private test profiles, and refer to
|
||||
sanitized evidence locations from public reports.
|
||||
|
||||
## Secret Boundary
|
||||
|
||||
Commit only `.env.example`. Keep `.env`, runtime data, logs, push-proxy
|
||||
|
|
|
|||
Loading…
Reference in a new issue