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>
78 lines
2.4 KiB
Dart
78 lines
2.4 KiB
Dart
import 'dart:async';
|
|
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<JSString?> _kcGetPushToken();
|
|
|
|
Future<String?> 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.
|
|
|
|
void setSession(String key, String value) =>
|
|
web.window.sessionStorage.setItem(key, value);
|
|
|
|
String? getSession(String key) => web.window.sessionStorage.getItem(key);
|
|
|
|
void removeSession(String key) => web.window.sessionStorage.removeItem(key);
|
|
|
|
void redirect(String url) => web.window.location.assign(url);
|
|
|
|
Map<String, String> currentQueryParameters() =>
|
|
Uri.parse(web.window.location.href).queryParameters;
|
|
|
|
/// Drop the OIDC callback path + `?code=…&state=…` from the address bar
|
|
/// without reloading (back to the app root).
|
|
void clearQuery() {
|
|
web.window.history.replaceState(null, '', '/');
|
|
}
|
|
|
|
/// Opens the OS file picker and reads the chosen file's bytes.
|
|
Future<({String name, List<int> bytes})?> pickFile() {
|
|
final completer = Completer<({String name, List<int> bytes})?>();
|
|
final input = web.HTMLInputElement()..type = 'file';
|
|
input.onchange = ((web.Event _) {
|
|
final files = input.files;
|
|
if (files == null || files.length == 0) {
|
|
completer.complete(null);
|
|
return;
|
|
}
|
|
final file = files.item(0)!;
|
|
final reader = web.FileReader();
|
|
reader.onload = ((web.Event _) {
|
|
final buffer = (reader.result as JSArrayBuffer).toDart;
|
|
completer.complete((name: file.name, bytes: buffer.asUint8List()));
|
|
}).toJS;
|
|
reader.onerror = ((web.Event _) => completer.complete(null)).toJS;
|
|
reader.readAsArrayBuffer(file);
|
|
}).toJS;
|
|
input.click();
|
|
return completer.future;
|
|
}
|
|
|
|
/// Triggers a browser download of an in-memory string (e.g. the CSV export).
|
|
void downloadText(
|
|
String filename,
|
|
String content, {
|
|
String mime = 'text/csv;charset=utf-8',
|
|
}) {
|
|
final blob = web.Blob([content.toJS].toJS, web.BlobPropertyBag(type: mime));
|
|
final url = web.URL.createObjectURL(blob);
|
|
web.HTMLAnchorElement()
|
|
..href = url
|
|
..download = filename
|
|
..click();
|
|
web.URL.revokeObjectURL(url);
|
|
}
|