From 530be36458764283a6bcbcf3f068f17880682236 Mon Sep 17 00:00:00 2001 From: linus Date: Thu, 10 Sep 2026 11:45:01 +0200 Subject: [PATCH] feat(client): FCM web push registration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- client/app/lib/api.dart | 15 +++++++++++ client/app/lib/browser_stub.dart | 3 +++ client/app/lib/browser_web.dart | 14 +++++++++++ client/app/web/firebase-messaging-sw.js | 21 ++++++++++++++++ client/app/web/index.html | 33 +++++++++++++++++++++++++ 5 files changed, 86 insertions(+) create mode 100644 client/app/web/firebase-messaging-sw.js diff --git a/client/app/lib/api.dart b/client/app/lib/api.dart index da4acc0..0305518 100644 --- a/client/app/lib/api.dart +++ b/client/app/lib/api.dart @@ -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; + // --- push --- + Future 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> channels(String kcId) async { final list = await _get('/chat/$kcId/channels') as List; @@ -746,6 +751,16 @@ class AppState extends ChangeNotifier { } _authError = null; notifyListeners(); + _registerForPush(); // best-effort, fire and forget + } + + Future _registerForPush() async { + try { + final pushToken = await browser.getPushToken(); + if (pushToken != null) await _api.registerDevice(pushToken); + } catch (_) { + // push is optional + } } Future _clear(SharedPreferences prefs) async { diff --git a/client/app/lib/browser_stub.dart b/client/app/lib/browser_stub.dart index 79f4cfa..1cce5fb 100644 --- a/client/app/lib/browser_stub.dart +++ b/client/app/lib/browser_stub.dart @@ -12,3 +12,6 @@ Future<({String name, List bytes})?> pickFile() async => throw UnsupportedError(_msg); void downloadText(String filename, String content, {String mime = 'text/plain'}) => throw UnsupportedError(_msg); + +/// No push on non-web platforms in this build. +Future getPushToken() async => null; diff --git a/client/app/lib/browser_web.dart b/client/app/lib/browser_web.dart index 10e507d..4d04e13 100644 --- a/client/app/lib/browser_web.dart +++ b/client/app/lib/browser_web.dart @@ -3,6 +3,20 @@ import 'dart:js_interop'; import 'package:web/web.dart' as web; +/// Provided by the inline Firebase bootstrap in web/index.html. Returns an FCM +/// registration token, or null if push isn't configured / permission denied. +@JS('kcGetPushToken') +external JSPromise _kcGetPushToken(); + +Future getPushToken() async { + try { + final result = await _kcGetPushToken().toDart; + return result?.toDart; + } catch (_) { + return null; + } +} + /// Per-tab storage for the PKCE verifier + CSRF state (cleared when the tab /// closes), and the little bit of `window` access the OIDC redirect needs. diff --git a/client/app/web/firebase-messaging-sw.js b/client/app/web/firebase-messaging-sw.js new file mode 100644 index 0000000..97a9097 --- /dev/null +++ b/client/app/web/firebase-messaging-sw.js @@ -0,0 +1,21 @@ +// Background handler for FCM web push. Keep the config in sync with +// window.KC_FIREBASE in index.html (a service worker can't read window). +// Fill in apiKey / appId from the Firebase console before enabling push. +importScripts('https://www.gstatic.com/firebasejs/10.13.2/firebase-app-compat.js'); +importScripts('https://www.gstatic.com/firebasejs/10.13.2/firebase-messaging-compat.js'); + +firebase.initializeApp({ + apiKey: 'REPLACE_ME', + projectId: 'konfi-castle-app', + messagingSenderId: '307226979593', + appId: 'REPLACE_ME', +}); + +firebase.messaging().onBackgroundMessage(function (payload) { + const n = payload.notification || {}; + self.registration.showNotification(n.title || 'KC-App', { + body: n.body || '', + icon: '/icons/Icon-192.png', + data: payload.data || {}, + }); +}); diff --git a/client/app/web/index.html b/client/app/web/index.html index 400faaa..320c4af 100644 --- a/client/app/web/index.html +++ b/client/app/web/index.html @@ -31,6 +31,39 @@ KC-App + + + + +