Merge pull request 'better config handling' (#768) from dev-760 into main
Some checks failed
ci/woodpecker/push/callbacks Pipeline is pending
ci/woodpecker/push/discovery Pipeline is pending
ci/woodpecker/push/frontend Pipeline is pending
ci/woodpecker/push/fx_ingestor Pipeline is pending
ci/woodpecker/push/fx_oracle Pipeline is pending
ci/woodpecker/push/gateway_aurora Pipeline is pending
ci/woodpecker/push/gateway_chain Pipeline is pending
ci/woodpecker/push/gateway_chsettle Pipeline is pending
ci/woodpecker/push/gateway_tron Pipeline is pending
ci/woodpecker/push/ledger Pipeline is pending
ci/woodpecker/push/notification Pipeline is pending
ci/woodpecker/push/payments_methods Pipeline is pending
ci/woodpecker/push/payments_orchestrator Pipeline is pending
ci/woodpecker/push/payments_quotation Pipeline is pending
ci/woodpecker/push/billing_fees Pipeline failed
ci/woodpecker/push/billing_documents Pipeline failed
ci/woodpecker/push/bff Pipeline failed

Reviewed-on: #768
This commit was merged in pull request #768.
This commit is contained in:
2026-03-17 13:22:15 +00:00
7 changed files with 92 additions and 64 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,74 @@
import 'dart:ui'; import 'dart:ui';
import 'dart:js_interop'; import 'dart:js_interop';
import 'package:pshared/config/common.dart'; import 'package:logging/logging.dart';
import 'package:pshared/config/common.dart';
/// Bind to the global JS `appConfig` (if it exists). /// Bind to the global JS `appConfig` (if it exists).
@JS() @JS()
external AppConfig? get appConfig; 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`. /// A staticInterop class for the JS object returned by `appConfig`.
@JS() @JS()
@staticInterop @staticInterop
@@ -19,8 +79,6 @@ extension AppConfigExtension on AppConfig {
external String? get apiProto; external String? get apiProto;
external String? get apiHost; external String? get apiHost;
external String? get apiEndpoint; external String? get apiEndpoint;
external String? get amplitudeSecret;
external String? get amplitudeServerZone;
external String? get posthogApiKey; external String? get posthogApiKey;
external String? get posthogHost; external String? get posthogHost;
external String? get defaultLocale; external String? get defaultLocale;
@@ -32,19 +90,20 @@ extension AppConfigExtension on AppConfig {
} }
class Constants extends CommonConstants { class Constants extends CommonConstants {
static const String _clientIdWeb = 'net.sendico.web-3f9a2c6e-7b4d-4e8a-9c2a-1d5f6b8e0a11';
// Just re-expose these from CommonConstants: // Just re-expose these from CommonConstants:
static Locale get defaultLocale => CommonConstants.defaultLocale; static Locale get defaultLocale => CommonConstants.defaultLocale;
static String get clientId => _clientIdWeb; static String get clientId => CommonConstants.clientId;
static String get accessTokenStorageKey => CommonConstants.accessTokenStorageKey; static String get accessTokenStorageKey =>
static String get refreshTokenStorageKey => CommonConstants.refreshTokenStorageKey; CommonConstants.accessTokenStorageKey;
static String get refreshTokenStorageKey =>
CommonConstants.refreshTokenStorageKey;
static String get currentOrgKey => CommonConstants.currentOrgKey; static String get currentOrgKey => CommonConstants.currentOrgKey;
static String get apiUrl => CommonConstants.apiUrl; static String get apiUrl => CommonConstants.apiUrl;
static String get serviceUrl => CommonConstants.serviceUrl; static String get serviceUrl => CommonConstants.serviceUrl;
static String get posthogApiKey => CommonConstants.posthogApiKey; static String get posthogApiKey => CommonConstants.posthogApiKey;
static String get posthogHost => CommonConstants.posthogHost; 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 deviceIdStorageKey => CommonConstants.deviceIdStorageKey;
static String get nilObjectRef => CommonConstants.nilObjectRef; static String get nilObjectRef => CommonConstants.nilObjectRef;
static Color get themeColor => CommonConstants.themeColor; static Color get themeColor => CommonConstants.themeColor;
@@ -55,12 +114,11 @@ class Constants extends CommonConstants {
if (config != null) { if (config != null) {
// Build a Dart Map from the JS objects properties: // Build a Dart Map from the JS objects properties:
final configJson = <String, dynamic>{ final runtimeConfigJson = <String, dynamic>{
'apiProto': config.apiProto, 'apiProto': config.apiProto,
'apiHost': config.apiHost, 'apiHost': config.apiHost,
'apiEndpoint': config.apiEndpoint, 'apiEndpoint': config.apiEndpoint,
'amplitudeSecret': config.amplitudeSecret, 'clientId': config.clientId,
'amplitudeServerZone': config.amplitudeServerZone,
'posthogApiKey': config.posthogApiKey, 'posthogApiKey': config.posthogApiKey,
'posthogHost': config.posthogHost, 'posthogHost': config.posthogHost,
'defaultLocale': config.defaultLocale, 'defaultLocale': config.defaultLocale,
@@ -68,14 +126,24 @@ class Constants extends CommonConstants {
'wsEndpoint': config.wsEndpoint, 'wsEndpoint': config.wsEndpoint,
'defaultDimensionLength': config.defaultDimensionLength, 'defaultDimensionLength': config.defaultDimensionLength,
'themeColor': config.themeColor, '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 { } else {
// No appConfig on JS side → just use the default Dart client ID. _runtimeConfigLogger.severe(
CommonConstants.clientId = _clientIdWeb; '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}")", apiProto: "$(js_escape "${API_PROTOCOL:-https}")",
apiHost: "$(js_escape "${SERVICE_HOST:-app.sendico.io}")", apiHost: "$(js_escape "${SERVICE_HOST:-app.sendico.io}")",
apiEndpoint: "$(js_escape "${API_ENDPOINT:-/api/v1}")", apiEndpoint: "$(js_escape "${API_ENDPOINT:-/api/v1}")",
amplitudeSecret: "$(js_escape "${AMPLITUDE_SECRET:-}")",
defaultLocale: "$(js_escape "${DEFAULT_LOCALE:-en}")", 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}")", wsProto: "$(js_escape "${WS_PROTOCOL:-wss}")",
wsEndpoint: "$(js_escape "${WS_ENDPOINT:-/ws}")" wsEndpoint: "$(js_escape "${WS_ENDPOINT:-/ws}")"
}; };