Files
pyramid/lib/core/fcm_push_service.dart
T
Bernd Steckmeister 25ed765a03 wip: Sicherungs-Commit aller Änderungen seit April + Arbeitsstruktur (CLAUDE.md, ROADMAP.md, PROGRESS.md, Autopilot)
6 Wochen uncommittete Arbeit (Voice-Channels, LiveKit-Manager, Settings-Modal u.v.m.)
als ein WIP-Commit gesichert, damit nichts verloren geht und der Pi den aktuellen
Stand klonen kann. Thematische Aufarbeitung: siehe ROADMAP M0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPrAGBxBT6GfPXzeWQ4AXb
2026-07-03 05:47:18 +02:00

184 lines
5.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:io';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:matrix/matrix.dart';
import 'package:shared_preferences/shared_preferences.dart';
// Self-hosted Sygnal instance on the Pi — must be configured with the
// Firebase service account for the 'chat-pyramid' Firebase project.
// push.element.io only supports Element's own apps and will actively reject
// chat.pyramid.pyramid pushkeys, causing the homeserver to auto-remove the pusher.
const _kGatewayUrl = 'https://push.steggi-matrix.work/_matrix/push/v1/notify';
const _kAppId = 'chat.pyramid.pyramid';
// ── Foreground-Setup & Pusher-Registrierung ───────────────────────────────────
Future<String> fcmDiagnostics(Client matrixClient) async {
if (!Platform.isAndroid) return 'Kein Android';
final buf = StringBuffer();
try {
await Firebase.initializeApp();
buf.writeln('✓ Firebase initialisiert');
} catch (e) {
buf.writeln('✗ Firebase-Init: $e');
return buf.toString();
}
try {
final settings = await FirebaseMessaging.instance.requestPermission();
buf.writeln('✓ Berechtigung: ${settings.authorizationStatus.name}');
} catch (e) {
buf.writeln('✗ Berechtigung: $e');
}
try {
final token = await FirebaseMessaging.instance.getToken();
if (token != null) {
buf.writeln('✓ FCM-Token: ${token.substring(0, 20)}');
} else {
buf.writeln('✗ FCM-Token: null');
}
} catch (e) {
buf.writeln('✗ FCM-Token: $e');
}
try {
final pushers = await matrixClient.getPushers() ?? [];
final fcmPushers = pushers.where((p) => p.appId == _kAppId).toList();
if (fcmPushers.isEmpty) {
buf.writeln('✗ Pusher: keiner bei Synapse registriert!');
} else {
buf.writeln('✓ Pusher: ${fcmPushers.length} registriert');
}
} catch (e) {
buf.writeln('✗ Pusher-Abfrage: $e');
}
return buf.toString();
}
Future<void> initFcm(Client matrixClient) async {
if (!Platform.isAndroid) return;
try {
await Firebase.initializeApp();
} catch (_) {
// Bereits initialisiert (main.dart) oder google-services.json fehlt weiter
}
// Background messages are handled by handleBackgroundMessage (background_push.dart)
// via FlutterFirebaseMessagingService. It decrypts E2EE events using the Matrix
// SDK and shows notifications via flutter_local_notifications with
// showsUserInterface:false so no Activity opens when the user replies.
// Notification-Berechtigung anfordern (Android 13+)
await FirebaseMessaging.instance.requestPermission();
// Foreground-Notifications unterdrücken — App zeigt selbst Nachrichten
await FirebaseMessaging.instance
.setForegroundNotificationPresentationOptions(
alert: false,
badge: false,
sound: false,
);
// Re-register pusher if token was refreshed while the app was killed.
final prefs = await SharedPreferences.getInstance();
final pendingToken = prefs.getString('flutter.fcm_pending_token');
if (pendingToken != null && matrixClient.isLogged()) {
await _registerPusher(matrixClient, pendingToken);
await prefs.remove('flutter.fcm_pending_token');
}
final token = pendingToken ?? await _getToken();
if (token != null && matrixClient.isLogged()) {
await _registerPusher(matrixClient, token);
// Save homeserver + token for PushService.kt (Kotlin native notification handler)
await _saveNotifCredentials(matrixClient);
}
// Bei Token-Refresh: Pusher + gespeicherte Credentials aktualisieren
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) async {
if (matrixClient.isLogged()) {
await _registerPusher(matrixClient, newToken);
await _saveNotifCredentials(matrixClient);
}
});
}
Future<void> _saveNotifCredentials(Client client) async {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('notif_homeserver', client.homeserver.toString());
await prefs.setString('notif_access_token', client.accessToken ?? '');
} catch (_) {}
}
Future<String?> _getToken() async {
try {
return await FirebaseMessaging.instance.getToken();
} catch (_) {
return null;
}
}
Future<void> _registerPusher(Client client, String fcmToken) async {
try {
await client.postPusher(
Pusher(
pushkey: fcmToken,
kind: 'http',
appId: _kAppId,
appDisplayName: 'Pyramid',
deviceDisplayName: client.deviceName ?? 'Android',
lang: 'de',
data: PusherData(
url: Uri.parse(_kGatewayUrl),
format: 'event_id_only',
),
),
append: true,
);
} catch (e) {
debugPrint('[FCM] Pusher-Registrierung fehlgeschlagen: $e');
}
}
/// Tries to register the FCM pusher and returns a human-readable result string.
Future<String> retryPusherRegistration(Client client) async {
if (!Platform.isAndroid) return 'Kein Android';
final buf = StringBuffer();
try {
String? token;
try {
token = await FirebaseMessaging.instance.getToken();
} catch (e) {
return '✗ FCM-Token: $e';
}
if (token == null) {
return '✗ FCM-Token: null (Google Play Services Problem?)';
}
buf.writeln('✓ FCM-Token: ${token.substring(0, 20)}');
buf.writeln(' Gateway: $_kGatewayUrl');
await client.postPusher(
Pusher(
pushkey: token,
kind: 'http',
appId: _kAppId,
appDisplayName: 'Pyramid',
deviceDisplayName: client.deviceName ?? 'Android',
lang: 'de',
data: PusherData(
url: Uri.parse(_kGatewayUrl),
format: 'event_id_only',
),
),
append: true,
);
buf.writeln('✓ Pusher registriert');
await _saveNotifCredentials(client);
buf.writeln('✓ Credentials gespeichert');
} catch (e) {
buf.writeln('✗ Fehler: $e');
}
return buf.toString().trim();
}