better config handling

This commit is contained in:
Stephan D
2026-03-17 14:21:56 +01:00
parent d2f9d61640
commit b6272876a8
7 changed files with 92 additions and 64 deletions

View File

@@ -23,11 +23,10 @@ AMPLI_ENVIRONMENT=production
API_PROTOCOL=https
SERVICE_HOST=app.sendico.io
API_ENDPOINT=/api/v1
CLIENT_ID=net.sendico.web-3f9a2c6e-7b4d-4e8a-9c2a-1d5f6b8e0a11
WS_PROTOCOL=wss
WS_ENDPOINT=/ws
AMPLITUDE_SECRET=c3d75b3e2520d708440acbb16b923e79
DEFAULT_LOCALE=en
DEFAULT_CURRENCY=EUR
PBM_S3_ENDPOINT=https://s3.sendico.io
PBM_S3_REGION=eu-central-1

View File

@@ -27,9 +27,8 @@ services:
API_PROTOCOL: ${API_PROTOCOL}
SERVICE_HOST: ${SERVICE_HOST}
API_ENDPOINT: ${API_ENDPOINT}
AMPLITUDE_SECRET: ${AMPLITUDE_SECRET}
DEFAULT_LOCALE: ${DEFAULT_LOCALE}
DEFAULT_CURRENCY: ${DEFAULT_CURRENCY}
CLIENT_ID: ${CLIENT_ID}
CADDY_ACME_EMAIL: ${CADDY_ACME_EMAIL}
ports:
- "0.0.0.0:${FRONTEND_HTTP_PORT}:80"

View File

@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:pshared/config/reader.dart';
void applyCommonConfiguration(
Map<String, dynamic> configJson, {
required String currentApiProto,
@@ -10,18 +9,12 @@ void applyCommonConfiguration(
required void Function(String) setApiHost,
required String currentApiEndpoint,
required void Function(String) setApiEndpoint,
required String currentAmplitudeSecret,
required void Function(String) setAmplitudeSecret,
required String currentAmplitudeServerZone,
required void Function(String) setAmplitudeServerZone,
required String currentPosthogApiKey,
required void Function(String) setPosthogApiKey,
required String currentPosthogHost,
required void Function(String) setPosthogHost,
required Locale currentDefaultLocale,
required void Function(Locale) setDefaultLocale,
required String currentDefaultCurrency,
required void Function(String) setDefaultCurrency,
required String currentWsProto,
required void Function(String) setWsProto,
required String currentWsEndpoint,
@@ -38,16 +31,6 @@ void applyCommonConfiguration(
setApiEndpoint(
readConfigString(configJson, 'apiEndpoint', currentApiEndpoint),
);
setAmplitudeSecret(
readConfigString(configJson, 'amplitudeSecret', currentAmplitudeSecret),
);
setAmplitudeServerZone(
readConfigString(
configJson,
'amplitudeServerZone',
currentAmplitudeServerZone,
),
);
setPosthogApiKey(
readConfigString(configJson, 'posthogApiKey', currentPosthogApiKey),
);
@@ -63,9 +46,6 @@ void applyCommonConfiguration(
),
),
);
setDefaultCurrency(
readConfigString(configJson, 'defaultCurrency', currentDefaultCurrency),
);
setWsProto(readConfigString(configJson, 'wsProto', currentWsProto));
setWsEndpoint(readConfigString(configJson, 'wsEndpoint', currentWsEndpoint));
setDefaultDimensionLength(

View File

@@ -1,17 +1,15 @@
import 'package:flutter/material.dart';
import 'package:pshared/config/apply.dart';
class CommonConstants {
static String apiProto = 'https';
static String apiHost = 'app.sendico.io';
static String apiEndpoint = '/api/v1';
static String amplitudeSecret = 'c3d75b3e2520d708440acbb16b923e79';
static String amplitudeServerZone = 'EU';
static String posthogApiKey =
'phc_lVhbruaZpxiQxppHBJpL36ARnPlkqbCewv6cauoceTN';
static String posthogApiKey = 'phc_lVhbruaZpxiQxppHBJpL36ARnPlkqbCewv6cauoceTN';
static String posthogHost = 'https://eu.i.posthog.com';
static Locale defaultLocale = const Locale('en');
static String defaultCurrency = 'EUR';
static int defaultDimensionLength = 500;
static String clientId = '';
static String wsProto = 'ws';
@@ -38,18 +36,12 @@ class CommonConstants {
setApiHost: (value) => apiHost = value,
currentApiEndpoint: apiEndpoint,
setApiEndpoint: (value) => apiEndpoint = value,
currentAmplitudeSecret: amplitudeSecret,
setAmplitudeSecret: (value) => amplitudeSecret = value,
currentAmplitudeServerZone: amplitudeServerZone,
setAmplitudeServerZone: (value) => amplitudeServerZone = value,
currentPosthogApiKey: posthogApiKey,
setPosthogApiKey: (value) => posthogApiKey = value,
currentPosthogHost: posthogHost,
setPosthogHost: (value) => posthogHost = value,
currentDefaultLocale: defaultLocale,
setDefaultLocale: (value) => defaultLocale = value,
currentDefaultCurrency: defaultCurrency,
setDefaultCurrency: (value) => defaultCurrency = value,
currentWsProto: wsProto,
setWsProto: (value) => wsProto = value,
currentWsEndpoint: wsEndpoint,

View File

@@ -5,13 +5,10 @@ class CommonConstants {
static String apiProto = 'http';
static String apiHost = 'localhost:8080';
static String apiEndpoint = '/api/v1';
static String amplitudeSecret = 'c3d75b3e2520d708440acbb16b923e79';
static String amplitudeServerZone = 'EU';
static String posthogApiKey =
'phc_lVhbruaZpxiQxppHBJpL36ARnPlkqbCewv6cauoceTN';
static String posthogHost = 'https://eu.i.posthog.com';
static Locale defaultLocale = const Locale('en');
static String defaultCurrency = 'EUR';
static int defaultDimensionLength = 500;
static String clientId = '';
static String wsProto = 'ws';
@@ -38,18 +35,12 @@ class CommonConstants {
setApiHost: (value) => apiHost = value,
currentApiEndpoint: apiEndpoint,
setApiEndpoint: (value) => apiEndpoint = value,
currentAmplitudeSecret: amplitudeSecret,
setAmplitudeSecret: (value) => amplitudeSecret = value,
currentAmplitudeServerZone: amplitudeServerZone,
setAmplitudeServerZone: (value) => amplitudeServerZone = value,
currentPosthogApiKey: posthogApiKey,
setPosthogApiKey: (value) => posthogApiKey = value,
currentPosthogHost: posthogHost,
setPosthogHost: (value) => posthogHost = value,
currentDefaultLocale: defaultLocale,
setDefaultLocale: (value) => defaultLocale = value,
currentDefaultCurrency: defaultCurrency,
setDefaultCurrency: (value) => defaultCurrency = value,
currentWsProto: wsProto,
setWsProto: (value) => wsProto = value,
currentWsEndpoint: wsEndpoint,

View File

@@ -1,14 +1,74 @@
import 'dart:ui';
import 'dart:js_interop';
import 'dart:js_interop';
import 'package:logging/logging.dart';
import 'package:pshared/config/common.dart';
/// Bind to the global JS `appConfig` (if it exists).
@JS()
external AppConfig? get appConfig;
final _runtimeConfigLogger = Logger('config.runtime');
const _requiredRuntimeConfigKeys = <String>{
'apiProto',
'apiHost',
'apiEndpoint',
'clientId',
'wsProto',
'wsEndpoint',
};
const _optionalRuntimeConfigKeys = <String>{
'posthogApiKey',
'posthogHost',
'defaultLocale',
'defaultDimensionLength',
'themeColor',
};
bool _isMissingRuntimeConfigValue(dynamic value) {
if (value == null) {
return true;
}
final text = value.toString().trim();
return text.isEmpty || text == 'undefined' || text == 'null';
}
List<String> _missingRuntimeConfigKeys(
Iterable<String> keys,
Map<String, dynamic> configJson,
) {
return keys
.where((key) => _isMissingRuntimeConfigValue(configJson[key]))
.toList()
..sort();
}
void _logRuntimeConfigDiagnostics(Map<String, dynamic> configJson) {
final missingRequired = _missingRuntimeConfigKeys(
_requiredRuntimeConfigKeys,
configJson,
);
final missingOptional = _missingRuntimeConfigKeys(
_optionalRuntimeConfigKeys,
configJson,
);
if (missingRequired.isNotEmpty) {
_runtimeConfigLogger.severe(
'Missing required runtime config: ${missingRequired.join(', ')}',
);
}
if (missingOptional.isNotEmpty) {
_runtimeConfigLogger.warning(
'Missing runtime config, using built-in defaults for: '
'${missingOptional.join(', ')}',
);
}
}
/// A staticInterop class for the JS object returned by `appConfig`.
@JS()
@staticInterop
@@ -19,8 +79,6 @@ extension AppConfigExtension on AppConfig {
external String? get apiProto;
external String? get apiHost;
external String? get apiEndpoint;
external String? get amplitudeSecret;
external String? get amplitudeServerZone;
external String? get posthogApiKey;
external String? get posthogHost;
external String? get defaultLocale;
@@ -32,19 +90,20 @@ extension AppConfigExtension on AppConfig {
}
class Constants extends CommonConstants {
static const String _clientIdWeb = 'net.sendico.web-3f9a2c6e-7b4d-4e8a-9c2a-1d5f6b8e0a11';
// Just re-expose these from CommonConstants:
static Locale get defaultLocale => CommonConstants.defaultLocale;
static String get clientId => _clientIdWeb;
static String get accessTokenStorageKey => CommonConstants.accessTokenStorageKey;
static String get refreshTokenStorageKey => CommonConstants.refreshTokenStorageKey;
static String get clientId => CommonConstants.clientId;
static String get accessTokenStorageKey =>
CommonConstants.accessTokenStorageKey;
static String get refreshTokenStorageKey =>
CommonConstants.refreshTokenStorageKey;
static String get currentOrgKey => CommonConstants.currentOrgKey;
static String get apiUrl => CommonConstants.apiUrl;
static String get serviceUrl => CommonConstants.serviceUrl;
static String get posthogApiKey => CommonConstants.posthogApiKey;
static String get posthogHost => CommonConstants.posthogHost;
static int get defaultDimensionLength => CommonConstants.defaultDimensionLength;
static int get defaultDimensionLength =>
CommonConstants.defaultDimensionLength;
static String get deviceIdStorageKey => CommonConstants.deviceIdStorageKey;
static String get nilObjectRef => CommonConstants.nilObjectRef;
static Color get themeColor => CommonConstants.themeColor;
@@ -55,12 +114,11 @@ class Constants extends CommonConstants {
if (config != null) {
// Build a Dart Map from the JS objects properties:
final configJson = <String, dynamic>{
final runtimeConfigJson = <String, dynamic>{
'apiProto': config.apiProto,
'apiHost': config.apiHost,
'apiEndpoint': config.apiEndpoint,
'amplitudeSecret': config.amplitudeSecret,
'amplitudeServerZone': config.amplitudeServerZone,
'clientId': config.clientId,
'posthogApiKey': config.posthogApiKey,
'posthogHost': config.posthogHost,
'defaultLocale': config.defaultLocale,
@@ -68,14 +126,24 @@ class Constants extends CommonConstants {
'wsEndpoint': config.wsEndpoint,
'defaultDimensionLength': config.defaultDimensionLength,
'themeColor': config.themeColor,
// If the JS side didnt supply a clientId, fall back to our Dart constant
'clientId': config.clientId ?? _clientIdWeb,
};
CommonConstants.applyConfiguration(configJson);
_logRuntimeConfigDiagnostics(runtimeConfigJson);
final missingRequired = _missingRuntimeConfigKeys(
_requiredRuntimeConfigKeys,
runtimeConfigJson,
);
if (missingRequired.isNotEmpty) {
throw StateError(
'Missing required runtime config: ${missingRequired.join(', ')}',
);
}
CommonConstants.applyConfiguration(runtimeConfigJson);
} else {
// No appConfig on JS side → just use the default Dart client ID.
CommonConstants.clientId = _clientIdWeb;
_runtimeConfigLogger.severe(
'window.appConfig is not defined; frontend runtime config is required.',
);
throw StateError('window.appConfig is not defined');
}
}
}

View File

@@ -11,9 +11,8 @@ window.appConfig = {
apiProto: "$(js_escape "${API_PROTOCOL:-https}")",
apiHost: "$(js_escape "${SERVICE_HOST:-app.sendico.io}")",
apiEndpoint: "$(js_escape "${API_ENDPOINT:-/api/v1}")",
amplitudeSecret: "$(js_escape "${AMPLITUDE_SECRET:-}")",
defaultLocale: "$(js_escape "${DEFAULT_LOCALE:-en}")",
defaultCurrency: "$(js_escape "${DEFAULT_CURRENCY:-EUR}")",
clientId: "$(js_escape "${CLIENT_ID:?CLIENT_ID is required}")",
wsProto: "$(js_escape "${WS_PROTOCOL:-wss}")",
wsEndpoint: "$(js_escape "${WS_ENDPOINT:-/ws}")"
};