diff --git a/ci/prod/.env.runtime b/ci/prod/.env.runtime index 63716a38..b92f76d3 100644 --- a/ci/prod/.env.runtime +++ b/ci/prod/.env.runtime @@ -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 diff --git a/ci/prod/compose/frontend.yml b/ci/prod/compose/frontend.yml index fd53da66..194213d8 100644 --- a/ci/prod/compose/frontend.yml +++ b/ci/prod/compose/frontend.yml @@ -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" diff --git a/frontend/pshared/lib/config/apply.dart b/frontend/pshared/lib/config/apply.dart index 0c7c3c6e..bd1c3d5d 100644 --- a/frontend/pshared/lib/config/apply.dart +++ b/frontend/pshared/lib/config/apply.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:pshared/config/reader.dart'; - void applyCommonConfiguration( Map 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( diff --git a/frontend/pshared/lib/config/common.dart b/frontend/pshared/lib/config/common.dart index 5ea4eefa..2b1bae47 100644 --- a/frontend/pshared/lib/config/common.dart +++ b/frontend/pshared/lib/config/common.dart @@ -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, diff --git a/frontend/pshared/lib/config/common_dev.dart b/frontend/pshared/lib/config/common_dev.dart index 5e829c9c..cce4c293 100644 --- a/frontend/pshared/lib/config/common_dev.dart +++ b/frontend/pshared/lib/config/common_dev.dart @@ -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, diff --git a/frontend/pshared/lib/config/web.dart b/frontend/pshared/lib/config/web.dart index 2fe981a0..b67dbab5 100644 --- a/frontend/pshared/lib/config/web.dart +++ b/frontend/pshared/lib/config/web.dart @@ -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 = { + 'apiProto', + 'apiHost', + 'apiEndpoint', + 'clientId', + 'wsProto', + 'wsEndpoint', +}; + +const _optionalRuntimeConfigKeys = { + '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 _missingRuntimeConfigKeys( + Iterable keys, + Map configJson, +) { + return keys + .where((key) => _isMissingRuntimeConfigValue(configJson[key])) + .toList() + ..sort(); +} + +void _logRuntimeConfigDiagnostics(Map 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 object’s properties: - final configJson = { + final runtimeConfigJson = { '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 didn’t 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'); } } } diff --git a/frontend/pweb/entrypoint.sh b/frontend/pweb/entrypoint.sh index fa025f19..b121f3c4 100755 --- a/frontend/pweb/entrypoint.sh +++ b/frontend/pweb/entrypoint.sh @@ -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}")" };