Files
Nitro_Render_V3/packages/configuration/src/ConfigurationManager.ts
T
2026-04-21 08:57:35 +02:00

198 lines
5.0 KiB
TypeScript

import { NitroLogger, NitroVersion } from '@nitrots/utils';
import { IConfigurationManager } from './IConfigurationManager';
export class ConfigurationManager implements IConfigurationManager
{
private _definitions: Map<string, unknown> = new Map();
private _config: any = {};
private _missingKeys: string[] = [];
constructor()
{
NitroVersion.sayHello();
}
public async init(): Promise<void>
{
await this.reloadConfiguration();
}
public async reloadConfiguration(): Promise<void>
{
try
{
this.resetConfiguration();
const defaultConfig = this.getDefaultConfig();
if(!defaultConfig) throw new Error('Missing NitroConfig: make sure window.NitroConfig is defined in index.html');
this.parseConfiguration(defaultConfig, true);
const configurationUrls = this.getValue<string[]>('config.urls').slice();
if(!configurationUrls || !configurationUrls.length) throw new Error('No config.urls defined in NitroConfig — expected an array like ["/renderer-config.json", "/ui-config.json"]');
for(const url of configurationUrls)
{
if(!url || !url.length) return;
let response: Response;
try
{
response = await fetch(url);
}
catch(fetchError)
{
throw new Error(`Failed to fetch config "${ url }" — check that the file exists and the server is reachable (${ fetchError.message })`);
}
if(response.status !== 200) throw new Error(`Failed to load config "${ url }" — server returned HTTP ${ response.status }`);
let json: any;
try
{
json = await response.json();
}
catch(parseError)
{
throw new Error(`Invalid JSON in config "${ url }" — check for syntax errors like trailing commas or missing quotes (${ parseError.message })`);
}
this.parseConfiguration(json);
}
}
catch (err)
{
throw new Error(err.message || String(err));
}
}
public resetConfiguration(): void
{
this._definitions.clear();
this._config = {};
this._missingKeys = [];
}
public parseConfiguration(data: { [index: string]: any }, overrides: boolean = false): boolean
{
if(!data) return false;
try
{
const regex = new RegExp(/\${(.*?)}/g);
for(const key in data)
{
let value = data[key];
if(typeof value === 'string') value = this.interpolate(value, regex);
if(this._definitions.has(key))
{
if(overrides) this.setValue(key, value);
}
else
{
this.setValue(key, value);
}
}
return true;
}
catch (e)
{
NitroLogger.error(e.stack);
return false;
}
}
public interpolate(value: string, regex: RegExp = null): string
{
if(!regex) regex = new RegExp(/\${(.*?)}/g);
const pieces = value.match(regex);
if(pieces && pieces.length)
{
for(const piece of pieces)
{
const existing = (this._definitions.get(this.removeInterpolateKey(piece)) as string);
if(existing) (value = value.replace(piece, existing));
}
}
if(value.indexOf('%timestamp%') >= 0)
{
value = value.replace(/%timestamp%/gi, Date.now().toString());
}
return value;
}
private removeInterpolateKey(value: string): string
{
return value.replace('${', '').replace('}', '');
}
public getValue<T>(key: string, value: T = null): T
{
let existing = this._definitions.get(key);
if(existing === undefined)
{
if(this._missingKeys.indexOf(key) >= 0) return value;
this._missingKeys.push(key);
NitroLogger.warn(`Missing configuration key: ${key}`);
existing = value;
}
return (existing as T);
}
public setValue<T>(key: string, value: T): void
{
const parts = key.split('.');
let last = this._config;
for(let i = 0; i < parts.length; i++)
{
const part = parts[i].toString();
if(i !== (parts.length - 1))
{
if(!last[part]) last[part] = {};
last = last[part];
continue;
}
last[part] = value;
}
this._definitions.set(key, value);
}
public getDefaultConfig(): { [index: string]: any }
{
return window.NitroConfig;
}
public get definitions(): Map<string, unknown>
{
return this._definitions;
}
}