nexo/apps/flutter-test/lib/main.dart
toki e2823b38e0 test(client-app): Flutter Web smoke 보완을 준비한다
브라우저 smoke의 선행 조건으로 test host를 web build 가능 상태로 만들고, 후속 runtime evidence 계획과 완료 로그를 함께 남긴다.
2026-06-08 10:52:46 +09:00

202 lines
6.6 KiB
Dart

import 'dart:async';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:nexo_messaging/nexo_messaging.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await _initializeFirebaseForHarness();
runApp(const MyApp());
}
Future<void> _initializeFirebaseForHarness() async {
try {
await Firebase.initializeApp();
} catch (error, stackTrace) {
debugPrint('[NexoClient] Firebase initialization skipped: $error');
debugPrintStack(stackTrace: stackTrace);
}
}
class MyApp extends StatefulWidget {
const MyApp({super.key, this.initializePlugin = true});
final bool initializePlugin;
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String? _deviceToken;
String? _lastNotification;
String? _lastOpened;
String? _lastNavigation;
NexoMessagingWebNotificationStatus? _webNotificationStatus;
NexoMessagingWebNotificationDisplayResult? _lastWebDisplay;
StreamSubscription<Map<String, dynamic>>? _notificationSubscription;
StreamSubscription<NotificationOpenedEvent>? _openedSubscription;
@override
void initState() {
super.initState();
final plugin = NexoMessagingPlugin.instance;
plugin.onDeviceTokenReady = (token) {
if (!mounted) return;
setState(() => _deviceToken = token);
};
plugin.onNavigateToChannel = (serverUrl, channelId) {
if (!mounted) return;
setState(() => _lastNavigation = 'channel:$serverUrl/$channelId');
};
plugin.onNavigateToThread = (serverUrl, rootId) {
if (!mounted) return;
setState(() => _lastNavigation = 'thread:$serverUrl/$rootId');
};
_notificationSubscription = plugin.onNotification.listen((event) {
if (!mounted) return;
setState(() => _lastNotification = _formatMap(event));
});
_openedSubscription = plugin.onNotificationOpened.listen((event) {
if (!mounted) return;
setState(() => _lastOpened = _formatOpened(event));
});
if (widget.initializePlugin) {
unawaited(_initializePlugin(plugin));
}
}
Future<void> _initializePlugin(NexoMessagingPlugin plugin) async {
try {
await _configureSmokeAuthToken(plugin);
await plugin.initialize();
} catch (error, stackTrace) {
debugPrint('[NexoClient] Failed to initialize messaging plugin: $error');
debugPrintStack(stackTrace: stackTrace);
}
}
Future<void> _configureSmokeAuthToken(NexoMessagingPlugin plugin) async {
const serverUrl = String.fromEnvironment('NEXO_SMOKE_SERVER_URL');
const authToken = String.fromEnvironment('NEXO_SMOKE_AUTH_TOKEN');
const identifier = String.fromEnvironment('NEXO_SMOKE_SERVER_IDENTIFIER');
if (serverUrl.isEmpty || authToken.isEmpty) return;
await plugin.setAuthToken(
serverUrl,
authToken,
identifier: identifier.isEmpty ? null : identifier,
);
debugPrint('[NexoClient] Smoke auth token configured');
}
@override
void dispose() {
_notificationSubscription?.cancel();
_openedSubscription?.cancel();
final plugin = NexoMessagingPlugin.instance;
plugin.onDeviceTokenReady = null;
plugin.onNavigateToChannel = null;
plugin.onNavigateToThread = null;
super.dispose();
}
String _formatMap(Map<String, dynamic> map) {
return map.entries.map((e) => '${e.key}: ${e.value}').join(', ');
}
String _formatOpened(NotificationOpenedEvent event) {
return 'server_url: ${event.serverUrl}, channel_id: ${event.channelId}, root_id: ${event.rootId}, is_crt_enabled: ${event.isCRTEnabled}';
}
String _formatWebSupport() {
if (_webNotificationStatus == null) return 'unknown';
return _webNotificationStatus!.isSupported ? 'yes' : 'no';
}
String _formatWebPermission() {
if (_webNotificationStatus == null) return 'unknown';
return _webNotificationStatus!.permission.name;
}
String _formatWebDisplay() {
if (_lastWebDisplay == null) return 'none';
if (!_lastWebDisplay!.isSupported) return 'unsupported';
if (!_lastWebDisplay!.permissionGranted) return 'permission denied';
return _lastWebDisplay!.success ? 'shown' : 'failed';
}
Future<void> _refreshWebNotificationStatus(NexoMessagingPlugin plugin) async {
try {
final status = await plugin.getWebNotificationPermissionStatus();
if (!mounted) return;
setState(() => _webNotificationStatus = status);
} catch (e) {
debugPrint('[NexoClient] Web permission refresh failed: $e');
}
}
Future<void> _requestWebNotificationPermission(
NexoMessagingPlugin plugin,
) async {
try {
final status = await plugin.requestWebNotificationPermission();
if (!mounted) return;
setState(() => _webNotificationStatus = status);
} catch (e) {
debugPrint('[NexoClient] Web permission request failed: $e');
}
}
void _showWebSmokeNotification(NexoMessagingPlugin plugin) {
final result = plugin.showWebForegroundNotification({
'title': 'Nexo Smoke',
'body': 'Web notification smoke test',
'server_url': 'https://nexo.example.com',
'channel_id': 'smoke-channel',
});
setState(() => _lastWebDisplay = result);
}
@override
Widget build(BuildContext context) {
final plugin = NexoMessagingPlugin.instance;
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Nexo client')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
Text('Device token: ${_deviceToken ?? 'pending'}'),
Text('Last notification: ${_lastNotification ?? 'none'}'),
Text('Last opened: ${_lastOpened ?? 'none'}'),
Text('Last navigation: ${_lastNavigation ?? 'none'}'),
Text('Web notification support: ${_formatWebSupport()}'),
Text(
'Web notification permission: ${_formatWebPermission()}',
key: const Key('web-permission-status'),
),
Text(
'Last web display: ${_formatWebDisplay()}',
key: const Key('web-display-status'),
),
ElevatedButton(
onPressed: () => unawaited(_refreshWebNotificationStatus(plugin)),
child: const Text('Refresh web permission'),
),
ElevatedButton(
onPressed: () =>
unawaited(_requestWebNotificationPermission(plugin)),
child: const Text('Request web permission'),
),
ElevatedButton(
onPressed: () => _showWebSmokeNotification(plugin),
child: const Text('Show web smoke notification'),
),
],
),
),
);
}
}