- 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
295 lines
9.5 KiB
Markdown
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.
|