From 5b139dfef844dfc5bf1fbdd8865ce7853d954044 Mon Sep 17 00:00:00 2001 From: duckietm Date: Thu, 19 Mar 2026 09:40:49 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=86=99=20Update=20better=20error=20handel?= =?UTF-8?q?ing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/assets/src/AssetManager.ts | 70 +++++++++++++------ .../avatar/src/AvatarAssetDownloadManager.ts | 26 +++++-- packages/avatar/src/AvatarRenderManager.ts | 48 ++++++++++--- .../avatar/src/EffectAssetDownloadManager.ts | 26 +++++-- .../configuration/src/ConfigurationManager.ts | 37 ++++++++-- .../localization/src/LocalizationManager.ts | 30 ++++++-- .../src/furniture/FurnitureDataLoader.ts | 26 +++++-- .../session/src/product/ProductDataLoader.ts | 26 +++++-- packages/utils/src/NitroVersion.ts | 21 +++--- 9 files changed, 240 insertions(+), 70 deletions(-) diff --git a/packages/assets/src/AssetManager.ts b/packages/assets/src/AssetManager.ts index ac2731a..481408a 100644 --- a/packages/assets/src/AssetManager.ts +++ b/packages/assets/src/AssetManager.ts @@ -77,18 +77,9 @@ export class AssetManager implements IAssetManager { if(!urls || !urls.length) return Promise.resolve(true); - try - { - await Promise.all(urls.map(url => this.downloadAsset(url))); + await Promise.all(urls.map(url => this.downloadAsset(url))); - return true; - } - catch (err) - { - NitroLogger.error(err); - } - - return false; + return true; } public async downloadAsset(url: string): Promise @@ -123,9 +114,18 @@ export class AssetManager implements IAssetManager if(url.endsWith('.nitro') || url.endsWith('.gif')) { - const response = await fetch(url); + let response: Response; - if(!response || response.status !== 200) return false; + try + { + response = await fetch(url); + } + catch(fetchErr) + { + throw new Error(`Could not fetch "${ url }" — is the URL correct and the server reachable? (${ fetchErr.message })`); + } + + if(!response || response.status !== 200) throw new Error(`Failed to load "${ url }" — server returned HTTP ${ response?.status ?? 'no response' }`); const arrayBuffer = await response.arrayBuffer(); @@ -137,19 +137,47 @@ export class AssetManager implements IAssetManager } else { - const animatedGif = AnimatedGIF.fromBuffer(arrayBuffer); - const texture = animatedGif.texture; + try + { + const animatedGif = AnimatedGIF.fromBuffer(arrayBuffer); + const texture = animatedGif.texture; - if(texture) this.setTexture(url, texture); + if(texture) this.setTexture(url, texture); + } + catch(gifErr) + { + const texture = await Assets.load(url); + + if(texture) this.setTexture(url, texture); + } } } else if(url.endsWith('.json')) { - const response = await fetch(url); + let response: Response; - if(!response || response.status !== 200) return false; + try + { + response = await fetch(url); + } + catch(fetchErr) + { + throw new Error(`Could not fetch "${ url }" — is the URL correct and the server reachable? (${ fetchErr.message })`); + } + + if(!response || response.status !== 200) throw new Error(`Failed to load "${ url }" — server returned HTTP ${ response?.status ?? 'no response' }`); + + let data: IAssetData; + + try + { + data = await response.json() as IAssetData; + } + catch(parseErr) + { + throw new Error(`Invalid JSON in "${ url }" — the URL may be wrong and returning an HTML page instead of JSON (${ parseErr.message })`); + } - const data = await response.json() as IAssetData; let texture: Texture = null; const imagePath = data?.spritesheet?.meta?.image; const fallbackImagePath = ((data?.name && data.name.length > 0) @@ -174,9 +202,7 @@ export class AssetManager implements IAssetManager } catch (err) { - NitroLogger.error(err); - - return false; + throw new Error(`Asset loading failed for "${ url }": ${ err.message || err }`); } } diff --git a/packages/avatar/src/AvatarAssetDownloadManager.ts b/packages/avatar/src/AvatarAssetDownloadManager.ts index 982023b..347108f 100644 --- a/packages/avatar/src/AvatarAssetDownloadManager.ts +++ b/packages/avatar/src/AvatarAssetDownloadManager.ts @@ -29,13 +29,31 @@ export class AvatarAssetDownloadManager const url = GetConfiguration().getValue('avatar.figuremap.url'); - if(!url || !url.length) throw new Error('Invalid figure map url'); + if(!url || !url.length) throw new Error('Missing "avatar.figuremap.url" in config — add the figure map URL to your renderer-config.json'); - const response = await fetch(url); + let response: Response; - if(response.status !== 200) throw new Error('Invalid figure map file'); + try + { + response = await fetch(url); + } + catch(fetchErr) + { + throw new Error(`Could not fetch figure map from "${ url }" — check "avatar.figuremap.url" in renderer-config.json (${ fetchErr.message })`); + } - const responseData = await response.json(); + if(response.status !== 200) throw new Error(`Failed to load figure map from "${ url }" — server returned HTTP ${ response.status }. Check "avatar.figuremap.url" in renderer-config.json`); + + let responseData: any; + + try + { + responseData = await response.json(); + } + catch(parseErr) + { + throw new Error(`Invalid JSON in figure map "${ url }" — the URL may be wrong. Check "avatar.figuremap.url" in renderer-config.json (${ parseErr.message })`); + } this.processFigureMap(responseData.libraries); diff --git a/packages/avatar/src/AvatarRenderManager.ts b/packages/avatar/src/AvatarRenderManager.ts index aa7a1e4..efd3645 100644 --- a/packages/avatar/src/AvatarRenderManager.ts +++ b/packages/avatar/src/AvatarRenderManager.ts @@ -70,13 +70,29 @@ export class AvatarRenderManager implements IAvatarRenderManager const url = GetConfiguration().getValue('avatar.actions.url'); - if(!url || !url.length) throw new Error('Invalid avatar action url'); + if(!url || !url.length) throw new Error('Missing "avatar.actions.url" in config — add the URL to your renderer-config.json'); - const response = await fetch(url); + let response: Response; - if(response.status !== 200) throw new Error('Invalid avatar action file'); + try + { + response = await fetch(url); + } + catch(fetchErr) + { + throw new Error(`Could not fetch avatar actions from "${ url }" — check "avatar.actions.url" in renderer-config.json (${ fetchErr.message })`); + } - this._structure.updateActions(await response.json()); + if(response.status !== 200) throw new Error(`Failed to load avatar actions from "${ url }" — server returned HTTP ${ response.status }. Check "avatar.actions.url" in renderer-config.json`); + + try + { + this._structure.updateActions(await response.json()); + } + catch(parseErr) + { + throw new Error(`Invalid JSON from "${ url }" — the URL may be wrong and returning an HTML page instead of JSON. Check "avatar.actions.url" in renderer-config.json (${ parseErr.message })`); + } } private async loadFigureData(): Promise @@ -87,13 +103,29 @@ export class AvatarRenderManager implements IAvatarRenderManager const url = GetConfiguration().getValue('avatar.figuredata.url'); - if(!url || !url.length) throw new Error('Invalid figure data url'); + if(!url || !url.length) throw new Error('Missing "avatar.figuredata.url" in config — add the URL to your renderer-config.json'); - const response = await fetch(url); + let response: Response; - if(response.status !== 200) throw new Error('Invalid figure data file'); + try + { + response = await fetch(url); + } + catch(fetchErr) + { + throw new Error(`Could not fetch figure data from "${ url }" — check "avatar.figuredata.url" in renderer-config.json (${ fetchErr.message })`); + } - this._structure.figureData.appendJSON(await response.json()); + if(response.status !== 200) throw new Error(`Failed to load figure data from "${ url }" — server returned HTTP ${ response.status }. Check "avatar.figuredata.url" in renderer-config.json`); + + try + { + this._structure.figureData.appendJSON(await response.json()); + } + catch(parseErr) + { + throw new Error(`Invalid JSON from "${ url }" — the URL may be wrong and returning an HTML page instead of JSON. Check "avatar.figuredata.url" in renderer-config.json (${ parseErr.message })`); + } this._structure.init(); } diff --git a/packages/avatar/src/EffectAssetDownloadManager.ts b/packages/avatar/src/EffectAssetDownloadManager.ts index 1594da3..9dae1d6 100644 --- a/packages/avatar/src/EffectAssetDownloadManager.ts +++ b/packages/avatar/src/EffectAssetDownloadManager.ts @@ -29,13 +29,31 @@ export class EffectAssetDownloadManager const url = GetConfiguration().getValue('avatar.effectmap.url'); - if(!url || !url.length) throw new Error('Invalid effect map url'); + if(!url || !url.length) throw new Error('Missing "avatar.effectmap.url" in config — add the effect map URL to your renderer-config.json'); - const response = await fetch(url); + let response: Response; - if(response.status !== 200) throw new Error('Invalid effect map file'); + try + { + response = await fetch(url); + } + catch(fetchErr) + { + throw new Error(`Could not fetch effect map from "${ url }" — check "avatar.effectmap.url" in renderer-config.json (${ fetchErr.message })`); + } - const responseData = await response.json(); + if(response.status !== 200) throw new Error(`Failed to load effect map from "${ url }" — server returned HTTP ${ response.status }. Check "avatar.effectmap.url" in renderer-config.json`); + + let responseData: any; + + try + { + responseData = await response.json(); + } + catch(parseErr) + { + throw new Error(`Invalid JSON in effect map "${ url }" — the URL may be wrong. Check "avatar.effectmap.url" in renderer-config.json (${ parseErr.message })`); + } this.processEffectMap(responseData.effects); diff --git a/packages/configuration/src/ConfigurationManager.ts b/packages/configuration/src/ConfigurationManager.ts index f4bcd99..8a2de97 100644 --- a/packages/configuration/src/ConfigurationManager.ts +++ b/packages/configuration/src/ConfigurationManager.ts @@ -22,27 +22,52 @@ export class ConfigurationManager implements IConfigurationManager try { this.resetConfiguration(); - this.parseConfiguration(this.getDefaultConfig(), true); + + 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('config.urls').slice(); - if(!configurationUrls || !configurationUrls.length) throw new Error('Invalid configuration urls'); + 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; - const response = await fetch(url); + let response: Response; - if(response.status !== 200) throw new Error('Invalid configuration file'); + 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 })`); + } - this.parseConfiguration(await response.json()); + 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); + throw new Error(err.message || String(err)); } } diff --git a/packages/localization/src/LocalizationManager.ts b/packages/localization/src/LocalizationManager.ts index e848953..b3ffc92 100644 --- a/packages/localization/src/LocalizationManager.ts +++ b/packages/localization/src/LocalizationManager.ts @@ -16,7 +16,7 @@ export class LocalizationManager implements ILocalizationManager { const urls = GetConfiguration().getValue('external.texts.url').slice(); - if(!urls || !urls.length) throw new Error('Invalid localization urls'); + if(!urls || !urls.length) throw new Error('Missing "external.texts.url" in config — add the localization URL to your ui-config.json'); for(let url of urls) { @@ -24,11 +24,31 @@ export class LocalizationManager implements ILocalizationManager url = GetConfiguration().interpolate(url); - const response = await fetch(url); + let response: Response; - if(response.status !== 200) throw new Error('Invalid localization file'); + try + { + response = await fetch(url); + } + catch(fetchErr) + { + throw new Error(`Could not fetch localization file "${ url }" — check "external.texts.url" in ui-config.json (${ fetchErr.message })`); + } - this.parseLocalization(await response.json()); + if(response.status !== 200) throw new Error(`Failed to load localization file "${ url }" — server returned HTTP ${ response.status }. Check "external.texts.url" in ui-config.json`); + + let data: any; + + try + { + data = await response.json(); + } + catch(parseErr) + { + throw new Error(`Invalid JSON in localization file "${ url }" — the URL may be wrong. Check "external.texts.url" in ui-config.json (${ parseErr.message })`); + } + + this.parseLocalization(data); } GetCommunication().registerMessageEvent(new BadgePointLimitsEvent(this.onBadgePointLimitsEvent.bind(this))); @@ -36,7 +56,7 @@ export class LocalizationManager implements ILocalizationManager catch (err) { - throw new Error(err); + throw new Error(err.message || String(err)); } } diff --git a/packages/session/src/furniture/FurnitureDataLoader.ts b/packages/session/src/furniture/FurnitureDataLoader.ts index 6f7a096..1d407c3 100644 --- a/packages/session/src/furniture/FurnitureDataLoader.ts +++ b/packages/session/src/furniture/FurnitureDataLoader.ts @@ -18,13 +18,31 @@ export class FurnitureDataLoader { const url = GetConfiguration().getValue('furnidata.url'); - if(!url || !url.length) throw new Error('invalid furni data url'); + if(!url || !url.length) throw new Error('Missing "furnidata.url" in config — add the furniture data URL to your renderer-config.json'); - const response = await fetch(url); + let response: Response; - if(response.status !== 200) throw new Error('Invalid furni data file'); + try + { + response = await fetch(url); + } + catch(fetchErr) + { + throw new Error(`Could not fetch furniture data from "${ url }" — check "furnidata.url" in renderer-config.json (${ fetchErr.message })`); + } - const responseData = await response.json(); + if(response.status !== 200) throw new Error(`Failed to load furniture data from "${ url }" — server returned HTTP ${ response.status }. Check "furnidata.url" in renderer-config.json`); + + let responseData: any; + + try + { + responseData = await response.json(); + } + catch(parseErr) + { + throw new Error(`Invalid JSON in furniture data "${ url }" — the URL may be wrong. Check "furnidata.url" in renderer-config.json (${ parseErr.message })`); + } if(responseData.roomitemtypes) this.parseFloorItems(responseData.roomitemtypes); diff --git a/packages/session/src/product/ProductDataLoader.ts b/packages/session/src/product/ProductDataLoader.ts index e8e846f..61da2de 100644 --- a/packages/session/src/product/ProductDataLoader.ts +++ b/packages/session/src/product/ProductDataLoader.ts @@ -15,13 +15,31 @@ export class ProductDataLoader { const url = GetConfiguration().getValue('productdata.url'); - if(!url || !url.length) throw new Error('invalid product data url'); + if(!url || !url.length) throw new Error('Missing "productdata.url" in config — add the product data URL to your renderer-config.json'); - const response = await fetch(url); + let response: Response; - if(response.status !== 200) throw new Error('Invalid product data file'); + try + { + response = await fetch(url); + } + catch(fetchErr) + { + throw new Error(`Could not fetch product data from "${ url }" — check "productdata.url" in renderer-config.json (${ fetchErr.message })`); + } - const responseData = await response.json(); + if(response.status !== 200) throw new Error(`Failed to load product data from "${ url }" — server returned HTTP ${ response.status }. Check "productdata.url" in renderer-config.json`); + + let responseData: any; + + try + { + responseData = await response.json(); + } + catch(parseErr) + { + throw new Error(`Invalid JSON in product data "${ url }" — the URL may be wrong. Check "productdata.url" in renderer-config.json (${ parseErr.message })`); + } this.parseProducts(responseData.productdata); } diff --git a/packages/utils/src/NitroVersion.ts b/packages/utils/src/NitroVersion.ts index 09f73c0..ecd2186 100644 --- a/packages/utils/src/NitroVersion.ts +++ b/packages/utils/src/NitroVersion.ts @@ -1,30 +1,25 @@ export class NitroVersion { - public static RENDERER_VERSION: string = '2.0.0'; - public static UI_VERSION: string = ''; + public static RENDERER_VERSION: string = '3.0.0'; + public static UI_VERSION: string = '3.0.4'; public static sayHello(): void { if(navigator.userAgent.toLowerCase().indexOf('chrome') > -1) { const args = [ - `\n %c %c %c Nitro ${NitroVersion.UI_VERSION} - Renderer ${NitroVersion.RENDERER_VERSION} %c %c %c https://discord.nitrodev.co %c %c \n\n`, - 'background: #ffffff; padding:5px 0;', - 'background: #ffffff; padding:5px 0;', - 'color: #ffffff; background: #000000; padding:5px 0;', - 'background: #ffffff; padding:5px 0;', - 'background: #ffffff; padding:5px 0;', - 'background: #000000; padding:5px 0;', - 'background: #ffffff; padding:5px 0;', - 'background: #ffffff; padding:5px 0;' + `\n %c NITRO %c UI ${NitroVersion.UI_VERSION} %c Renderer ${NitroVersion.RENDERER_VERSION} %c \n`, + 'background: #1a3a5c; color: #ffffff; font-size: 14px; font-weight: bold; padding: 8px 12px; border-radius: 6px 0 0 6px;', + 'background: #2a5f8f; color: #e0ecf8; font-size: 12px; padding: 8px 10px;', + 'background: #3d7ab5; color: #e0ecf8; font-size: 12px; padding: 8px 10px; border-radius: 0 6px 6px 0;', + 'background: transparent;' ]; self.console.log(...args); } - else if(self.console) { - self.console.log(`Nitro ${NitroVersion.UI_VERSION} - Renderer ${NitroVersion.RENDERER_VERSION} `); + self.console.log(`Nitro UI ${NitroVersion.UI_VERSION} - Renderer ${NitroVersion.RENDERER_VERSION}`); } } }