11import { Injectable , inject } from '@angular/core' ;
2- import { HttpClient } from '@angular/common/http' ;
2+ import { HttpClient , HttpHeaders } from '@angular/common/http' ;
3+ import { firstValueFrom } from 'rxjs' ;
4+
5+ interface EnvConfig {
6+ logLevel ?: number ;
7+ configEndpoint ?: boolean ;
8+ ENVIRONMENT ?: string ;
9+ BANNER_COLOUR ?: string ;
10+ API_PATH ?: string ;
11+ API_LOCATION ?: string ;
12+ KEYCLOAK_CLIENT_ID ?: string ;
13+ KEYCLOAK_URL ?: string ;
14+ KEYCLOAK_REALM ?: string ;
15+ KEYCLOAK_ENABLED ?: boolean ;
16+ ANALYTICS_API_URL ?: string | null ;
17+ ANALYTICS_DEBUG ?: boolean ;
18+ REDIRECT_KEY ?: string ;
19+ }
20+
21+ // env.js sets window.__env before Angular loads
22+ declare global {
23+ interface Window { __env : EnvConfig ; }
24+ }
325
426//
527// This service/class provides a centralized place to persist config values
@@ -14,46 +36,118 @@ export class ConfigService {
1436 private _baseLayerName = 'World Topographic' ; // NB: must match a valid base layer name
1537 private _lists = [ ] ;
1638 private _regions = [ ] ;
17- private configuration = { } ;
39+ private configuration : EnvConfig = { } ;
40+ private configLoaded = false ;
1841
1942 /**
20- * Initialize the Config Service. Get configuration data from front-end build, or back-end if nginx
21- * is configured to pass the /config endpoint to a dynamic service that returns JSON.
43+ * Initialize the Config Service.
44+ * Get configuration from env.js first, then from API if configEndpoint is true.
45+ * Pattern follows reserve-rec-public.
2246 */
23- public async init ( ) {
24- try {
25- this . configuration = await this . httpClient . get ( '/api/config' ) . toPromise ( ) ;
47+ public async init ( ) : Promise < void > {
48+ // Start with env.js values (loaded before Angular via script tag in index.html)
49+ this . configuration = window . __env || { } ;
2650
27- console . log ( 'Configuration:' , this . configuration ) ;
28- if ( this . configuration [ 'debugMode' ] ) {
29- console . log ( 'Configuration:' , this . configuration ) ;
30- }
31- } catch ( e ) {
32- // Not configured
33- console . log ( 'Error getting configuration:' , e ) ;
34- this . configuration = window [ '__env' ] ;
35- if ( this . configuration [ 'debugMode' ] ) {
36- console . log ( 'Configuration:' , this . configuration ) ;
51+ if ( this . configuration . logLevel === 0 ) {
52+ console . log ( 'Initial configuration from env.js:' , this . configuration ) ;
53+ }
54+
55+ // If configEndpoint is true (deployed environments), fetch config from API
56+ if ( this . configuration . configEndpoint === true ) {
57+ try {
58+ const apiConfig = await this . getConfigFromApi ( ) ;
59+ // Merge API config (API values take precedence)
60+ this . configuration = { ...this . configuration , ...apiConfig } ;
61+ } catch ( e ) {
62+ // If API fails, continue with env.js values
63+ console . error ( 'Error getting API configuration, using env.js defaults:' , e ) ;
3764 }
3865 }
3966
67+ this . configLoaded = true ;
68+
69+ if ( this . configuration . logLevel === 0 ) {
70+ console . log ( 'Final configuration:' , this . configuration ) ;
71+ }
72+
73+ // Get the Lists and set the Regions datasets
4074 try {
41- // Get the Lists and set the Regions datasets
42- const lists = await this . httpClient . get < any > ( `${ this . configuration [ 'API_LOCATION' ] } ${ this . configuration [ 'API_PATH' ] } /search?pageSize=1000&dataset=List` , { } ) . toPromise ( ) ;
75+ const apiPath = this . getApiPath ( ) ;
76+ const lists = await firstValueFrom (
77+ this . httpClient . get < any > ( `${ apiPath } /search?pageSize=1000&dataset=List` )
78+ ) ;
4379 this . _lists = lists [ 0 ] . searchResults ;
4480 this . populateRegionsList ( ) ;
4581 } catch ( e ) {
46- if ( this . configuration [ 'debugMode' ] ) {
47- console . log ( 'Getting list error:' , e ) ;
82+ console . error ( 'Error loading lists:' , e ) ;
83+ }
84+ }
85+
86+ /**
87+ * Get the API path for making API calls.
88+ * Uses API_LOCATION + API_PATH, otherwise falls back to relative /api.
89+ */
90+ private getApiPath ( ) : string {
91+ if ( this . configuration . API_LOCATION ) {
92+ return this . configuration . API_LOCATION + ( this . configuration . API_PATH || '' ) ;
93+ }
94+ // Fallback to relative path (for deployed environments with nginx proxy)
95+ return '/api' ;
96+ }
97+
98+ /**
99+ * Fetch configuration from API endpoint.
100+ * Retries with fibonacci backoff if API is unavailable.
101+ */
102+ private async getConfigFromApi ( ) : Promise < EnvConfig > {
103+ let n1 = 0 ;
104+ let n2 = 1 ;
105+ let attempts = 0 ;
106+ const maxAttempts = 5 ;
107+
108+ while ( attempts < maxAttempts ) {
109+ try {
110+ const headers = new HttpHeaders ( ) . set ( 'Authorization' , 'config' ) ;
111+ // Use API_LOCATION if set, otherwise relative /api/config (nginx proxies in deployed env)
112+ const apiBase = this . configuration . API_LOCATION || '' ;
113+ const url = apiBase + '/api/config' ;
114+
115+ const response = await firstValueFrom (
116+ this . httpClient . get < any > ( url , { headers, observe : 'response' } )
117+ ) ;
118+ return response . body ?. data || response . body ;
119+ } catch ( err ) {
120+ attempts ++ ;
121+ if ( attempts >= maxAttempts ) {
122+ throw err ;
123+ }
124+ console . log ( `Config API attempt ${ attempts } failed, retrying...` ) ;
125+ const delay = n1 + n2 ;
126+ await this . delay ( delay * 1000 ) ;
127+ n1 = n2 ;
128+ n2 = delay ;
48129 }
49130 }
131+ throw new Error ( 'Failed to load config from API' ) ;
132+ }
133+
134+ private async delay ( ms : number ) : Promise < void > {
135+ return new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
136+ }
50137
51- return Promise . resolve ( ) ;
138+ get logLevel ( ) : number {
139+ // Can be overridden by js console
140+ return window . __env ?. logLevel ?? 4 ;
52141 }
53- get config ( ) : any {
142+
143+ get config ( ) : EnvConfig {
54144 return this . configuration ;
55145 }
56146
147+ get isConfigLoaded ( ) : boolean {
148+ return this . configLoaded ;
149+ }
150+
57151 // getters/setters
58152 get lists ( ) : any [ ] { return this . _lists ; }
59153 get regions ( ) : any [ ] { return this . _regions ; }
0 commit comments