nexo/packages/messaging_flutter/README.md
toki 81bd42756d feat: m-client-validation task and README updates
- Add m-client-validation task with 02_native_test plan (PLAN-cloud-G07.md)
- Update roadmap current.md and product-foundation phase
- Update client-validation milestone
- Add README for apps/flutter-test and packages/messaging_flutter
2026-05-29 09:26:51 +09:00

295 lines
9.5 KiB
Markdown

# nexo_messaging
Android-first Flutter plugin for the nexo messaging and push-notification
runtime.
The plugin owns the push-notification domain that should not be duplicated in
each consuming app: native FCM handling, notification display, notification tap
events, inline replies, dismiss handling, ACK delivery, device-token storage,
auth-token storage, and server signing-key storage.
## SDK Ownership Boundary
This package is the nexo-owned embedded messaging SDK. Its Android
implementation follows the nexo server/push contract, Android and FCM platform
changes, and Flutter plugin registration behavior. It does not follow or mirror
the Mattermost mobile app source tree; that app remains under `apps/mattermost/`
as an upstream clone.
## Platform Status
| Platform | Status | Notes |
| --- | --- | --- |
| Android | Production target | Native FCM service, Room storage, JJWT verification, OkHttp ACK client, notification builder, inline reply receiver, dismiss receiver |
| iOS | Stub | Plugin scaffold exists, behavior is not implemented yet |
| macOS | Stub | Plugin scaffold exists, behavior is not implemented yet |
## Repository Layout
```text
packages/messaging_flutter/
lib/ # Public Dart API and Flutter-facing behavior
android/ # Android native plugin implementation
ios/ # iOS plugin scaffold
macos/ # macOS plugin scaffold
test/ # Dart unit tests for the plugin contract
../../apps/flutter-test/ # Flutter test host
```
The workspace `apps/flutter-test/` app validates plugin integration without
turning this repository into a product-app workspace. The original Mattermost app
belongs under `apps/mattermost/`, not in the test host.
## How It Works
```mermaid
graph TD
A[nexo Server] -->|Secure JWT Push| B(Android FCM Service)
B -->|Verify signing key| C{Valid?}
C -->|Yes| D[Store payload and build notification]
C -->|No| E[Drop push]
D -->|Send ACK| A
D -->|Display notification| F(Android status bar)
F -->|Tap| G[Flutter opened event]
F -->|Inline reply| H[Reply receiver]
H -->|Send reply| A
G -->|Stream and callbacks| I(Host app navigation)
```
## Host App Responsibilities
The plugin isolates push handling, but the host application still owns app-level
integration:
1. Configure Firebase for the host app, including `google-services.json` under
`android/app/`.
2. Initialize Firebase as required by the host app before push registration.
3. Call `NexoMessagingPlugin.instance.initialize()` during app startup.
4. Save auth tokens and signing keys after login.
5. Clear auth tokens on logout.
6. Send the formatted device token to the nexo server.
7. Connect notification-open callbacks to the host app router.
## Installation
For local development or another app in the same workspace, use a path
dependency:
```yaml
dependencies:
nexo_messaging:
path: ../../packages/messaging_flutter
```
For a private Git dependency, point the consuming app at the repository and pin
the branch, tag, or commit according to your release process.
## Dart Usage
Import the public API:
```dart
import 'package:nexo_messaging/nexo_messaging.dart';
```
Initialize the plugin:
```dart
await NexoMessagingPlugin.instance.initialize();
```
Listen to raw notification events:
```dart
NexoMessagingPlugin.instance.onNotification.listen((data) {
// data includes the native payload plus a push type.
});
```
Listen to notification-open events:
```dart
NexoMessagingPlugin.instance.onNotificationOpened.listen((event) {
final serverUrl = event.serverUrl;
final channelId = event.channelId;
final rootId = event.rootId;
});
```
Register navigation callbacks:
```dart
NexoMessagingPlugin.instance.onNavigateToChannel = (
String serverUrl,
String channelId,
) {
// Navigate to the channel in the host app.
};
NexoMessagingPlugin.instance.onNavigateToThread = (
String serverUrl,
String rootId,
) {
// Navigate to the CRT thread in the host app.
};
```
Handle device-token registration:
```dart
NexoMessagingPlugin.instance.onDeviceTokenReady = (String deviceToken) {
// Example format: android_rn-v2:YOUR_FCM_TOKEN
// Send this token to the nexo server.
};
```
Store credentials and signing keys:
```dart
await NexoMessagingPlugin.instance.setAuthToken(
'https://nexo.example.com',
'USER_AUTH_TOKEN',
identifier: 'user_session_id',
);
await NexoMessagingPlugin.instance.setSigningKey(
'https://nexo.example.com',
'SERVER_PUBLIC_SIGNING_KEY',
);
await NexoMessagingPlugin.instance.clearAuthToken(
'https://nexo.example.com',
);
final token = await NexoMessagingPlugin.instance.getDeviceToken();
```
## Event Types
The Dart API currently recognizes these native event types:
| Type | Meaning |
| --- | --- |
| `message` | A nexo message notification |
| `clear` | Notification clear or dismiss event |
| `session` | Session-related event |
| `token_refresh` | Device-token refresh event |
| `opened` | User opened a notification |
`NotificationOpenedEvent` parses `server_url`, `channel_id`, `root_id`, and
`is_crt_enabled`. If CRT is enabled and `root_id` is present, the plugin calls
`onNavigateToThread`; otherwise it calls `onNavigateToChannel` when a channel is
available.
## Android Notes
The Android manifest declares the plugin's own
`NexoFirebaseMessagingService` and removes the default FlutterFire
messaging components with `tools:node="remove"`. This avoids duplicate FCM
service registration when the host app also depends on `firebase_messaging`.
Host apps should not redeclare duplicate FCM services unless they intentionally
coordinate that behavior with this plugin.
Android Gradle tests require a configured Android SDK through `ANDROID_HOME` or
`apps/flutter-test/android/local.properties`.
## Development Commands
Run Dart analysis and unit tests from the repository root:
```sh
flutter analyze
flutter test
```
Run `flutter-test` checks from the app path:
```sh
cd apps/flutter-test
flutter analyze
flutter test
flutter test integration_test
```
Run Android native unit tests:
```sh
cd apps/flutter-test/android
./gradlew testDebugUnitTest
```
If the local machine does not have an Android SDK or Android device/emulator,
use the separate SSH-based Android test environment documented in
[`docs/android-test-environment.md`](docs/android-test-environment.md).
## Test Strategy
Use deterministic tests for repository-owned behavior. The example integration
test can inject a debug native event through the plugin method channel so the
EventChannel and opened-routing path can run without Firebase FCM.
The Android implementation remains a nexo-owned embedded SDK surface in these
tests. Test coverage should follow the server/push contract, Android and FCM
platform behavior, and Flutter plugin registration rather than the Mattermost
mobile app source tree.
Use the repository tests as the source of truth for plugin behavior:
1. Root `test/` covers Dart API behavior, event parsing, callback routing, and
device-token formatting.
2. `android/src/test/` should cover native payload parsing, signing-key lookup,
local storage, notification behavior, and ACK request behavior.
3. `apps/flutter-test/test/` covers the thin Flutter test app.
4. `apps/flutter-test/integration_test/` covers real plugin registration,
method-channel behavior, event-channel behavior, notification-open routing,
and manifest integration on a device or emulator using the test/debug
injection path.
5. Manual smoke tests cover true Firebase FCM delivery and real nexo server ACK
flows because they depend on external infrastructure.
## Manual Smoke Checklist
Use this checklist before promoting a plugin change to a consuming app. These
checks require external Firebase and nexo server infrastructure, so they are not
expected to pass in a headless repository test run.
Prerequisites:
- Firebase is configured for the host app with `android/app/google-services.json`.
- The host app has called `NexoMessagingPlugin.instance.initialize()`.
- The test user has a server URL, auth token, and signing key saved through the
plugin API.
- The nexo server can send a push payload with `ack_id`, `server_url` or
resolvable `server_id`, `channel_id`, and a valid signature.
Evidence to capture:
- `logcat` lines from `NexoFirebaseMessagingService` for receipt, ACK, and
signature handling.
- A screenshot or device recording of the displayed notification.
- Server-side ACK/reply request logs showing response status.
- Host app UI evidence for `onNotificationOpened`, `onNavigateToChannel`, and
`onNavigateToThread` routing.
- [ ] FCM payload wakes `NexoFirebaseMessagingService`.
- [ ] Valid nexo push payload passes signing-key verification.
- [ ] Invalid or unsigned payload is dropped.
- [ ] System notification displays the expected title, message, avatar, and CRT
threading behavior.
- [ ] ACK request is sent to the nexo server and receives a successful
response.
- [ ] Tapping a notification launches or resumes the app and emits
`onNotificationOpened`.
- [ ] Channel notifications call `onNavigateToChannel`.
- [ ] CRT thread notifications call `onNavigateToThread`.
- [ ] Inline reply action sends the reply through the native receiver.
- [ ] Dismiss action clears native notification records as expected.
- [ ] Device token is stored with the `android_rn-v2:` prefix.
## Current Limitations
- Android is the only implemented runtime target.
- iOS and macOS are scaffolded but no-op.
- Full FCM delivery cannot be reliably automated in a headless test runner.
- The `apps/flutter-test/` app should stay focused on package integration behavior.