feat(client): FCM web push registration

web/index.html loads the Firebase compat SDK and defines
window.kcGetPushToken() — inits Firebase from window.KC_FIREBASE, asks for
notification permission, registers the service worker and returns an FCM
token (or null if not configured / denied). web/firebase-messaging-sw.js
shows background notifications.

browser_web.dart exposes getPushToken() over that JS function (stub returns
null off-web). After every successful login AppState fires
_registerForPush() -> POST /api/push/register, best-effort.

Config placeholders carry the known values (projectId konfi-castle-app,
messagingSenderId 307226979593); apiKey / appId / vapidKey still say
REPLACE_ME, so push stays inert until they're filled in — the app runs
either way. flutter analyze/test/build web green.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 11:45:01 +02:00
co-authored by Claude Sonnet 5
parent cc663c7e17
commit 530be36458
5 changed files with 86 additions and 0 deletions
+15
View File
@@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'browser.dart' as browser;
import 'oidc.dart';
/// Backend base URL. Override at build/run time with
@@ -662,6 +663,10 @@ class Api {
'gemeindeId': gemeindeId,
}) as Map<String, dynamic>;
// --- push ---
Future<void> registerDevice(String token, {String platform = 'web'}) =>
_post('/push/register', {'token': token, 'platform': platform});
// --- chat: REST for channels/history; live send/receive is the /chat WS ---
Future<List<ChatChannel>> channels(String kcId) async {
final list = await _get('/chat/$kcId/channels') as List<dynamic>;
@@ -746,6 +751,16 @@ class AppState extends ChangeNotifier {
}
_authError = null;
notifyListeners();
_registerForPush(); // best-effort, fire and forget
}
Future<void> _registerForPush() async {
try {
final pushToken = await browser.getPushToken();
if (pushToken != null) await _api.registerDevice(pushToken);
} catch (_) {
// push is optional
}
}
Future<void> _clear(SharedPreferences prefs) async {