Document secure runtime configuration

This commit is contained in:
Lorenzune
2026-05-06 06:27:40 +02:00
parent 71171dc205
commit 851d82f93f
9 changed files with 1701 additions and 181 deletions
+279
View File
@@ -0,0 +1,279 @@
# Local Development Setup with `yarn start`
This guide explains how to run Nitro locally with Vite, using:
- local UI on `http://localhost:5173`;
- local API/emulator on `http://localhost:2096`;
- local WebSocket on `ws://localhost:2096`;
- remote plain assets and gamedata, so you do not need to copy the full `client/nitro` folder locally.
## 1. Start the emulator
Inside `Arcturus-Morningstar-Extended/Emulator`, start the emulator with WebSocket enabled.
Recommended local `config.ini` values:
```ini
ws.enabled=true
ws.host=0.0.0.0
ws.port=2096
ws.whitelist=*
ws.ip.header=
crypto.ws.enabled=0
nitro.secure.assets.enabled=false
nitro.secure.api.enabled=false
```
For local development, it is easier to disable:
- `crypto.ws.enabled`;
- `nitro.secure.assets.enabled`;
- `nitro.secure.api.enabled`.
This keeps debugging simple and avoids the secure runtime layer.
## 2. `public/configuration/client-mode.json`
File:
```txt
Nitro-V3/public/configuration/client-mode.json
```
Recommended local config:
```json
{
"distObfuscationEnabled": true,
"secureAssetsEnabled": false,
"secureApiEnabled": false,
"apiBaseUrl": "http://localhost:2096",
"plainConfigBaseUrl": "http://localhost:5173/configuration/",
"plainGamedataBaseUrl": "https://hotel.example.com/client/nitro/gamedata/"
}
```
Notes:
- `secureAssetsEnabled=false` avoids `/nitro-sec/file`.
- `secureApiEnabled=false` avoids encrypted `/api/*` requests.
- `apiBaseUrl` must point to your local emulator.
- `plainGamedataBaseUrl` can stay remote if you do not have gamedata copied locally.
If you want everything local, use:
```json
"plainGamedataBaseUrl": "http://localhost:5173/client/nitro/gamedata/"
```
but the files must really exist under:
```txt
Nitro-V3/public/client/nitro/gamedata/
```
## 3. `public/configuration/renderer-config.json`
File:
```txt
Nitro-V3/public/configuration/renderer-config.json
```
Minimum local values:
```json
{
"socket.url": "ws://localhost:2096",
"api.url": "http://localhost:2096",
"crypto.ws.enabled": false,
"gamedata.url": "https://hotel.example.com/client/nitro/gamedata",
"external.texts.url": [
"${gamedata.url}/ExternalTexts.json",
"${gamedata.url}/UITexts.json"
],
"furnidata.url": "${gamedata.url}/FurnitureData.json?t=%timestamp%",
"productdata.url": "${gamedata.url}/ProductData.json?t=%timestamp%",
"avatar.actions.url": "${gamedata.url}/HabboAvatarActions.json?t=%timestamp%",
"avatar.figuredata.url": "${gamedata.url}/FigureData.json?t=%timestamp%",
"avatar.figuremap.url": "${gamedata.url}/FigureMap.json?t=%timestamp%",
"avatar.effectmap.url": "${gamedata.url}/EffectMap.json?t=%timestamp%",
"login.endpoint": "${api.url}/api/auth/login",
"login.register.endpoint": "${api.url}/api/auth/register",
"login.forgot.endpoint": "${api.url}/api/auth/forgot-password",
"login.logout.endpoint": "${api.url}/api/auth/logout",
"login.remember.endpoint": "${api.url}/api/auth/remember",
"login.health.endpoint": "${api.url}/api/health",
"login.health.method": "GET",
"login.check-email.endpoint": "${api.url}/api/auth/check-email",
"login.check-username.endpoint": "${api.url}/api/auth/check-username",
"login.register.imaging.url": "${api.url}/api/avatar/imaging",
"login.news.url": "${api.url}/api/auth/news",
"badges.custom.list.endpoint": "${api.url}/api/badges/custom",
"badges.custom.create.endpoint": "${api.url}/api/badges/custom",
"badges.custom.update.endpoint": "${api.url}/api/badges/custom/%badgeId%",
"badges.custom.delete.endpoint": "${api.url}/api/badges/custom/%badgeId%",
"badges.custom.texts.endpoint": "${api.url}/api/badges/custom/texts"
}
```
Important:
- Do not use `https://localhost:2096/nitro-sec/file` locally if `secureAssetsEnabled=false`.
- Do not use `ws://192.168.x.x/:2096`; it is malformed. Use `ws://localhost:2096` or `ws://192.168.x.x:2096`.
## 4. `public/configuration/ui-config.json`
File:
```txt
Nitro-V3/public/configuration/ui-config.json
```
For the login view, you can use remote plain images:
```json
{
"loginview": {
"images": {
"background": "https://hotel.example.com/client/nitro/images/reception/background_gradient_apr25.png",
"background.colour": "#6eadc8",
"drape": "https://hotel.example.com/client/nitro/images/reception/drape.png",
"left": "https://hotel.example.com/client/nitro/images/reception/mute_reception_backdrop_left.png",
"right": "https://hotel.example.com/client/nitro/images/reception/background_right.png"
}
}
}
```
If you see `ERR_NAME_NOT_RESOLVED`, the configured domain does not exist or is not reachable.
## 5. Database-backed news
Login news should come from the database through the emulator.
In renderer config use:
```json
"login.news.url": "${api.url}/api/auth/news"
```
The emulator reads from:
```txt
ui_news
```
Reference SQL:
```txt
Arcturus-Morningstar-Extended/Database Updates/013_UI_Client_News.sql
```
Main columns:
- `title`
- `body`
- `image`
- `link_text`
- `link_url`
- `enabled`
- `sort_order`
`public/configuration/news.json` can stay as a mock/fallback only, but it is not the correct production flow.
## 6. Start Nitro
Inside `Nitro-V3`:
```bash
yarn start
```
Open:
```txt
http://localhost:5173
```
Recommendation: use `localhost`, not `192.168.x.x`, because cookies and API sessions are host-based and can otherwise cause `401 Unauthorized`.
## 7. Common errors
### `Unable to load renderer-config.json`
Check:
```txt
public/configuration/client-mode.json
```
It must contain:
```json
"secureAssetsEnabled": false
```
### `Invalid JSON ... Unexpected token '<'`
The client requested JSON, but Vite returned HTML.
This happens when a URL points to a file that does not exist, for example:
```txt
http://localhost:5173/client/nitro/gamedata/ExternalTexts.json
```
Fix:
- use remote plain gamedata;
- or copy the gamedata files into `public/client/nitro/gamedata`.
### WebSocket `1006`
Check:
```json
"socket.url": "ws://localhost:2096"
```
and emulator config:
```ini
ws.enabled=true
ws.port=2096
```
### Custom badges `401 Unauthorized`
This is normal if you are not logged in or if you open Nitro from a different host.
Use:
```txt
http://localhost:5173
```
and API:
```txt
http://localhost:2096
```
## 8. Difference from production
Local `yarn start`:
```html
<script type="module" src="/src/bootstrap.ts"></script>
```
Production build:
```html
<script src="/configuration/bootstrap.js"></script>
```
Do not mix the two flows.
+279
View File
@@ -0,0 +1,279 @@
# Setup locale con `yarn start`
Questa guida serve per avviare Nitro in locale con Vite, usando:
- UI locale su `http://localhost:5173`;
- API/emulatore locale su `http://localhost:2096`;
- WebSocket locale su `ws://localhost:2096`;
- asset e gamedata remoti plain, così non devi copiare tutta la cartella `client/nitro`.
## 1. Avvia l'emulatore
Nel repo `Arcturus-Morningstar-Extended/Emulator`, avvia l'emulatore con WebSocket attivo.
Nel tuo `config.ini` locale usa valori tipo:
```ini
ws.enabled=true
ws.host=0.0.0.0
ws.port=2096
ws.whitelist=*
ws.ip.header=
crypto.ws.enabled=0
nitro.secure.assets.enabled=false
nitro.secure.api.enabled=false
```
Per il locale è meglio tenere spenti:
- `crypto.ws.enabled`;
- `nitro.secure.assets.enabled`;
- `nitro.secure.api.enabled`.
Così puoi debuggare senza layer secure in mezzo.
## 2. `public/configuration/client-mode.json`
File:
```txt
Nitro-V3/public/configuration/client-mode.json
```
Config locale consigliato:
```json
{
"distObfuscationEnabled": true,
"secureAssetsEnabled": false,
"secureApiEnabled": false,
"apiBaseUrl": "http://localhost:2096",
"plainConfigBaseUrl": "http://localhost:5173/configuration/",
"plainGamedataBaseUrl": "https://hotel.example.com/client/nitro/gamedata/"
}
```
Note:
- `secureAssetsEnabled=false` evita `/nitro-sec/file`.
- `secureApiEnabled=false` evita cifratura `/api/*`.
- `apiBaseUrl` deve puntare all'emulatore locale.
- `plainGamedataBaseUrl` può rimanere remoto se non hai gamedata copiato in locale.
Se vuoi tutto locale, usa:
```json
"plainGamedataBaseUrl": "http://localhost:5173/client/nitro/gamedata/"
```
ma devi avere davvero i file sotto:
```txt
Nitro-V3/public/client/nitro/gamedata/
```
## 3. `public/configuration/renderer-config.json`
File:
```txt
Nitro-V3/public/configuration/renderer-config.json
```
Valori minimi locali:
```json
{
"socket.url": "ws://localhost:2096",
"api.url": "http://localhost:2096",
"crypto.ws.enabled": false,
"gamedata.url": "https://hotel.example.com/client/nitro/gamedata",
"external.texts.url": [
"${gamedata.url}/ExternalTexts.json",
"${gamedata.url}/UITexts.json"
],
"furnidata.url": "${gamedata.url}/FurnitureData.json?t=%timestamp%",
"productdata.url": "${gamedata.url}/ProductData.json?t=%timestamp%",
"avatar.actions.url": "${gamedata.url}/HabboAvatarActions.json?t=%timestamp%",
"avatar.figuredata.url": "${gamedata.url}/FigureData.json?t=%timestamp%",
"avatar.figuremap.url": "${gamedata.url}/FigureMap.json?t=%timestamp%",
"avatar.effectmap.url": "${gamedata.url}/EffectMap.json?t=%timestamp%",
"login.endpoint": "${api.url}/api/auth/login",
"login.register.endpoint": "${api.url}/api/auth/register",
"login.forgot.endpoint": "${api.url}/api/auth/forgot-password",
"login.logout.endpoint": "${api.url}/api/auth/logout",
"login.remember.endpoint": "${api.url}/api/auth/remember",
"login.health.endpoint": "${api.url}/api/health",
"login.health.method": "GET",
"login.check-email.endpoint": "${api.url}/api/auth/check-email",
"login.check-username.endpoint": "${api.url}/api/auth/check-username",
"login.register.imaging.url": "${api.url}/api/avatar/imaging",
"login.news.url": "${api.url}/api/auth/news",
"badges.custom.list.endpoint": "${api.url}/api/badges/custom",
"badges.custom.create.endpoint": "${api.url}/api/badges/custom",
"badges.custom.update.endpoint": "${api.url}/api/badges/custom/%badgeId%",
"badges.custom.delete.endpoint": "${api.url}/api/badges/custom/%badgeId%",
"badges.custom.texts.endpoint": "${api.url}/api/badges/custom/texts"
}
```
Importante:
- Non usare `https://localhost:2096/nitro-sec/file` in locale se `secureAssetsEnabled=false`.
- Non usare `ws://192.168.x.x/:2096`: è malformato. Usa `ws://localhost:2096` oppure `ws://192.168.x.x:2096`.
## 4. `public/configuration/ui-config.json`
File:
```txt
Nitro-V3/public/configuration/ui-config.json
```
Per la login view puoi usare immagini remote plain:
```json
{
"loginview": {
"images": {
"background": "https://hotel.example.com/client/nitro/images/reception/background_gradient_apr25.png",
"background.colour": "#6eadc8",
"drape": "https://hotel.example.com/client/nitro/images/reception/drape.png",
"left": "https://hotel.example.com/client/nitro/images/reception/mute_reception_backdrop_left.png",
"right": "https://hotel.example.com/client/nitro/images/reception/background_right.png"
}
}
}
```
Se vedi `ERR_NAME_NOT_RESOLVED`, il dominio configurato non esiste o non è raggiungibile.
## 5. News dal database
Le news della login devono arrivare dal database tramite l'emulatore.
Nel renderer config usa:
```json
"login.news.url": "${api.url}/api/auth/news"
```
L'emulatore legge dalla tabella:
```txt
ui_news
```
SQL di riferimento:
```txt
Arcturus-Morningstar-Extended/Database Updates/013_UI_Client_News.sql
```
Colonne principali:
- `title`
- `body`
- `image`
- `link_text`
- `link_url`
- `enabled`
- `sort_order`
`public/configuration/news.json` può rimanere solo come mock/fallback, ma non è il flow corretto.
## 6. Avvio Nitro
Nel repo `Nitro-V3`:
```bash
yarn start
```
Apri:
```txt
http://localhost:5173
```
Consiglio: usa `localhost`, non `192.168.x.x`, perché cookie e sessioni API possono cambiare host e causare `401 Unauthorized`.
## 7. Errori comuni
### `Unable to load renderer-config.json`
Controlla:
```txt
public/configuration/client-mode.json
```
Deve avere:
```json
"secureAssetsEnabled": false
```
### `Invalid JSON ... Unexpected token '<'`
Vuol dire che il client ha chiesto un JSON, ma Vite ha risposto HTML.
Succede quando un URL punta a un file che non esiste, per esempio:
```txt
http://localhost:5173/client/nitro/gamedata/ExternalTexts.json
```
Soluzione:
- usa gamedata remoto plain;
- oppure copia davvero i gamedata in `public/client/nitro/gamedata`.
### WebSocket `1006`
Controlla:
```json
"socket.url": "ws://localhost:2096"
```
e nel config emulator:
```ini
ws.enabled=true
ws.port=2096
```
### Custom badges `401 Unauthorized`
È normale se non sei loggato o se apri Nitro da un host diverso.
Usa:
```txt
http://localhost:5173
```
e API:
```txt
http://localhost:2096
```
## 8. Differenza con produzione
Locale con `yarn start`:
```html
<script type="module" src="/src/bootstrap.ts"></script>
```
Produzione buildata:
```html
<script src="/configuration/bootstrap.js"></script>
```
Non mischiare i due flow.
+365
View File
@@ -0,0 +1,365 @@
# Secure Runtime Production Setup
Quick setup guide for running Nitro with:
- configuration and gamedata served through `/nitro-sec/file`;
- encrypted runtime `/api/*` calls;
- obfuscated production bundles loaded as `.dat`.
Replace the example domains with your real domains:
- `https://hotel.example.com`
- `https://nitro.example.com:2096`
## 1. Build Nitro
Inside the `Nitro-V3` repository:
```bash
yarn build
```
Then publish the `dist` folder to your web server, for example:
```txt
C:/inetpub/wwwroot/hotel/nitro
```
The deployed folder should contain at least:
```txt
configuration/
assets/
asset-loader.js
index.html
src/
```
## 2. `configuration/client-mode.json`
File:
```txt
Nitro-V3/dist/configuration/client-mode.json
```
Secure production configuration:
```json
{
"distObfuscationEnabled": true,
"secureAssetsEnabled": true,
"secureApiEnabled": true,
"apiBaseUrl": "https://nitro.example.com:2096",
"plainConfigBaseUrl": "https://hotel.example.com/configuration/",
"plainGamedataBaseUrl": "https://hotel.example.com/client/nitro/gamedata/"
}
```
Meaning:
- `distObfuscationEnabled: true` loads `app.js.dat` and `app.css.dat`.
- `secureAssetsEnabled: true` loads `renderer-config.json`, `ui-config.json`, and gamedata through `/nitro-sec/file`.
- `secureApiEnabled: true` automatically encrypts `/api/*` requests.
- `apiBaseUrl` must point to the emulator/API.
- `plainConfigBaseUrl` and `plainGamedataBaseUrl` are fallbacks when secure assets are disabled.
## 3. `configuration/renderer-config.json`
File:
```txt
Nitro-V3/dist/configuration/renderer-config.json
```
Important values:
```json
{
"socket.url": "wss://nitro.example.com:2096",
"api.url": "https://nitro.example.com:2096",
"gamedata.url": "https://nitro.example.com:2096/nitro-sec/file?kind=gamedata&file=",
"external.texts.url": [
"${gamedata.url}/ExternalTexts.json",
"${gamedata.url}/UITexts.json"
],
"furnidata.url": "${gamedata.url}/FurnitureData.json?t=%timestamp%",
"productdata.url": "${gamedata.url}/ProductData.json?t=%timestamp%",
"avatar.actions.url": "${gamedata.url}/HabboAvatarActions.json?t=%timestamp%",
"avatar.figuredata.url": "${gamedata.url}/FigureData.json?t=%timestamp%",
"avatar.figuremap.url": "${gamedata.url}/FigureMap.json?t=%timestamp%",
"avatar.effectmap.url": "${gamedata.url}/EffectMap.json?t=%timestamp%",
"crypto.ws.enabled": true
}
```
If you are not using WebSocket crypto yet, use:
```json
"crypto.ws.enabled": false
```
## 4. `configuration/ui-config.json`
File:
```txt
Nitro-V3/dist/configuration/ui-config.json
```
Static image and camera URLs can remain plain:
```json
{
"camera.url": "https://hotel.example.com/client/camera/",
"thumbnails.url": "https://hotel.example.com/client/camera/thumbnail/%thumbnail%.png"
}
```
Non-sensitive images can stay static. JSON configuration and gamedata should go through the secure endpoint.
## 5. Emulator `config.ini`
Inside `Arcturus-Morningstar-Extended`, edit the emulator config:
```txt
Emulator/config.ini
```
Production example:
```ini
ws.enabled=true
ws.host=0.0.0.0
ws.port=2096
ws.whitelist=https://hotel.example.com
ws.ip.header=CF-Connecting-IP
crypto.ws.enabled=1
nitro.secure.assets.enabled=true
nitro.secure.api.enabled=true
nitro.secure.config.root=C:/inetpub/wwwroot/hotel/nitro/configuration
nitro.secure.gamedata.root=C:/inetpub/wwwroot/hotel/nitro/client/nitro/gamedata
nitro.secure.master_key=change-this-to-a-long-random-secret
login.remember.enabled=true
login.remember.duration.days=30
login.remember.jwt.secret=change-this-too-if-you-use-remember-me
```
Notes:
- `nitro.secure.config.root` must point to the folder containing `renderer-config.json`, `ui-config.json`, and `client-mode.json`.
- `nitro.secure.gamedata.root` must point to the live gamedata folder.
- Files are read live from disk: if you update a JSON file, a new browser refresh reads the new version.
- `nitro.secure.master_key` must be secret and stable. Never put it in public files.
## 6. Cloudflare
If you use Cloudflare:
1. Keep the proxy enabled for the website domain `hotel.example.com`.
2. Make sure Cloudflare supports/proxies the port used by `nitro.example.com:2096`.
3. Always use HTTPS/WSS in the browser:
```json
"api.url": "https://nitro.example.com:2096",
"socket.url": "wss://nitro.example.com:2096"
```
If you get CORS errors, check:
```ini
ws.whitelist=https://hotel.example.com
```
## 7. IIS / `.dat` MIME type
If obfuscated `.dat` assets are enabled, IIS must serve them correctly.
Add this MIME type:
```txt
Extension: .dat
MIME type: application/octet-stream
```
Without it, the browser can receive 404 even when the file exists.
## 8. Final checklist
- `client-mode.json` has `secureAssetsEnabled=true`.
- `client-mode.json` has `secureApiEnabled=true`.
- `renderer-config.json` uses `/nitro-sec/file?kind=gamedata&file=`.
- `api.url` points to `https://nitro.example.com:2096`.
- `socket.url` points to `wss://nitro.example.com:2096`.
- `config.ini` has the correct `nitro.secure.config.root`.
- `config.ini` has the correct `nitro.secure.gamedata.root`.
- `config.ini` has a stable `nitro.secure.master_key`.
- IIS knows the `.dat` MIME type.
- Restart the emulator after changing `config.ini`.
- Refresh the browser after changing JSON files in `configuration` or `gamedata`.
## 9. Temporarily disable secure mode
For quick debugging, only change `client-mode.json`:
```json
{
"distObfuscationEnabled": false,
"secureAssetsEnabled": false,
"secureApiEnabled": false,
"apiBaseUrl": "https://nitro.example.com:2096",
"plainConfigBaseUrl": "https://hotel.example.com/configuration/",
"plainGamedataBaseUrl": "https://hotel.example.com/client/nitro/gamedata/"
}
```
Then hard refresh the browser.
## 10. `configuration/bootstrap.js`
File:
```txt
Nitro-V3/dist/configuration/bootstrap.js
```
This is the first loader when you use the external secure mode.
It does three things:
1. opens an ECDH session with the emulator through `/nitro-sec/bootstrap`;
2. downloads encrypted `client-mode.json` through `/nitro-sec/file?kind=config`;
3. downloads encrypted `asset-loader.js` and imports it as a JavaScript module.
### Value to check
Inside `bootstrap.js` there is:
```js
const API_BASE = "https://nitro.example.com:2096";
```
It must point to your public emulator/API URL.
In production:
```js
const API_BASE = "https://nitro.example.com:2096";
```
In local development:
```js
const API_BASE = "http://localhost:2096";
```
If `bootstrap.js` fails, it automatically falls back to the plain loader:
```txt
configuration/asset-loader.js
```
So `asset-loader.js` must always exist inside the `configuration` folder.
## 11. `configuration/asset-loader.js`
File:
```txt
Nitro-V3/dist/configuration/asset-loader.js
```
This loader loads the actual bundle:
- if `distObfuscationEnabled=true`
- it loads `app.css.dat`;
- it loads `app.js.dat`;
- it decodes, decompresses, and imports the bundle from a blob.
- if `distObfuscationEnabled=false`
- it loads `assets/app.css`;
- it loads `assets/app.js`.
### Required files in production
With obfuscation enabled, these files must exist:
```txt
assets/app.css.dat
assets/app.js.dat
configuration/asset-loader.js
configuration/bootstrap.js
configuration/client-mode.json
```
With obfuscation disabled, these files must exist:
```txt
assets/app.css
assets/app.js
configuration/asset-loader.js
configuration/client-mode.json
```
## 12. `index.html`
`index.html` should stay minimal.
Secure production example:
```html
<div id="root"></div>
<script src="/configuration/bootstrap.js"></script>
```
Vite development example:
```html
<div id="root"></div>
<script type="module" src="/src/bootstrap.ts"></script>
```
Do not mix the two flows:
- production build: use `configuration/bootstrap.js`;
- `yarn start` development: use `/src/bootstrap.ts`.
## 13. Files inside `/configuration`
The `configuration` folder should contain:
```txt
asset-loader.js
bootstrap.js
client-mode.json
renderer-config.json
ui-config.json
adsense.json optional
hotlooks.json if register hot looks are enabled
UITexts.json if separate UI texts are enabled
```
Login news should not live in `news.json` in production. They come from the database through:
```json
"login.news.url": "${api.url}/api/auth/news"
```
The emulator reads from the `ui_news` table.
With `secureAssetsEnabled=true`, client-loaded files go through:
```txt
https://nitro.example.com:2096/nitro-sec/file?kind=config&file=...
```
The emulator reads them from:
```ini
nitro.secure.config.root=C:/inetpub/wwwroot/hotel/nitro/configuration
```
If you add new JSON/JS files inside `configuration` and want to protect them, they must be requested through the secure endpoint or loaded through `bootstrap.js`.
+365
View File
@@ -0,0 +1,365 @@
# Setup Secure Runtime in produzione
Guida rapida per avviare Nitro con:
- configurazioni e gamedata serviti da `/nitro-sec/file`;
- API `/api/*` cifrate dal wrapper runtime;
- bundle buildati offuscati come `.dat`.
Negli esempi usa i tuoi domini reali al posto di:
- `https://hotel.example.com`
- `https://nitro.example.com:2096`
## 1. Build Nitro
Nel repo `Nitro-V3`:
```bash
yarn build
```
Poi pubblica la cartella `dist` nel web server del sito, ad esempio:
```txt
C:/inetpub/wwwroot/hotel/nitro
```
La struttura pubblicata deve contenere almeno:
```txt
configuration/
assets/
asset-loader.js
index.html
src/
```
## 2. `configuration/client-mode.json`
File:
```txt
Nitro-V3/dist/configuration/client-mode.json
```
Configurazione produzione secure:
```json
{
"distObfuscationEnabled": true,
"secureAssetsEnabled": true,
"secureApiEnabled": true,
"apiBaseUrl": "https://nitro.example.com:2096",
"plainConfigBaseUrl": "https://hotel.example.com/configuration/",
"plainGamedataBaseUrl": "https://hotel.example.com/client/nitro/gamedata/"
}
```
Significato:
- `distObfuscationEnabled: true` carica `app.js.dat` e `app.css.dat`.
- `secureAssetsEnabled: true` carica `renderer-config.json`, `ui-config.json` e gamedata da `/nitro-sec/file`.
- `secureApiEnabled: true` cifra automaticamente le chiamate `/api/*`.
- `apiBaseUrl` deve puntare all'emulatore/API.
- `plainConfigBaseUrl` e `plainGamedataBaseUrl` restano fallback quando spegni secure assets.
## 3. `configuration/renderer-config.json`
File:
```txt
Nitro-V3/dist/configuration/renderer-config.json
```
Valori importanti:
```json
{
"socket.url": "wss://nitro.example.com:2096",
"api.url": "https://nitro.example.com:2096",
"gamedata.url": "https://nitro.example.com:2096/nitro-sec/file?kind=gamedata&file=",
"external.texts.url": [
"${gamedata.url}/ExternalTexts.json",
"${gamedata.url}/UITexts.json"
],
"furnidata.url": "${gamedata.url}/FurnitureData.json?t=%timestamp%",
"productdata.url": "${gamedata.url}/ProductData.json?t=%timestamp%",
"avatar.actions.url": "${gamedata.url}/HabboAvatarActions.json?t=%timestamp%",
"avatar.figuredata.url": "${gamedata.url}/FigureData.json?t=%timestamp%",
"avatar.figuremap.url": "${gamedata.url}/FigureMap.json?t=%timestamp%",
"avatar.effectmap.url": "${gamedata.url}/EffectMap.json?t=%timestamp%",
"crypto.ws.enabled": true
}
```
Se non usi ancora WebSocket crypto, metti:
```json
"crypto.ws.enabled": false
```
## 4. `configuration/ui-config.json`
File:
```txt
Nitro-V3/dist/configuration/ui-config.json
```
Qui puoi lasciare immagini e camera su URL statici normali:
```json
{
"camera.url": "https://hotel.example.com/client/camera/",
"thumbnails.url": "https://hotel.example.com/client/camera/thumbnail/%thumbnail%.png"
}
```
Le immagini non sensibili possono rimanere statiche. I JSON/gamedata invece passano dal secure endpoint.
## 5. `config.ini` dell'emulatore
Nel repo `Arcturus-Morningstar-Extended`, file usato dall'emulatore:
```txt
Emulator/config.ini
```
Esempio produzione:
```ini
ws.enabled=true
ws.host=0.0.0.0
ws.port=2096
ws.whitelist=https://hotel.example.com
ws.ip.header=CF-Connecting-IP
crypto.ws.enabled=1
nitro.secure.assets.enabled=true
nitro.secure.api.enabled=true
nitro.secure.config.root=C:/inetpub/wwwroot/hotel/nitro/configuration
nitro.secure.gamedata.root=C:/inetpub/wwwroot/hotel/nitro/client/nitro/gamedata
nitro.secure.master_key=change-this-to-a-long-random-secret
login.remember.enabled=true
login.remember.duration.days=30
login.remember.jwt.secret=change-this-too-if-you-use-remember-me
```
Note:
- `nitro.secure.config.root` deve puntare alla cartella dove ci sono `renderer-config.json`, `ui-config.json`, `client-mode.json`.
- `nitro.secure.gamedata.root` deve puntare alla cartella live dei gamedata.
- I file vengono letti live da disco: se cambi un JSON, un nuovo refresh pagina legge la nuova versione.
- `nitro.secure.master_key` deve restare segreta e stabile. Non metterla nei file pubblici.
## 6. Cloudflare
Se usi Cloudflare:
1. Lascia la nuvoletta attiva sul dominio web `hotel.example.com`.
2. Per `nitro.example.com:2096`, assicurati che Cloudflare supporti/proxy il traffico sulla porta usata.
3. Usa sempre HTTPS/WSS lato browser:
```json
"api.url": "https://nitro.example.com:2096",
"socket.url": "wss://nitro.example.com:2096"
```
Se vedi errori CORS, controlla:
```ini
ws.whitelist=https://hotel.example.com
```
## 7. IIS / MIME `.dat`
Se usi gli asset offuscati `.dat`, IIS deve servirli.
Aggiungi MIME type:
```txt
Extension: .dat
MIME type: application/octet-stream
```
Senza questo, il browser può dare 404 anche se il file esiste davvero.
## 8. Checklist finale
- `client-mode.json` ha `secureAssetsEnabled=true`.
- `client-mode.json` ha `secureApiEnabled=true`.
- `renderer-config.json` usa `/nitro-sec/file?kind=gamedata&file=`.
- `api.url` punta a `https://nitro.example.com:2096`.
- `socket.url` punta a `wss://nitro.example.com:2096`.
- `config.ini` ha `nitro.secure.config.root` corretto.
- `config.ini` ha `nitro.secure.gamedata.root` corretto.
- `config.ini` ha `nitro.secure.master_key` stabile.
- IIS conosce il MIME `.dat`.
- Dopo modifiche a `config.ini`, riavvia l'emulatore.
- Dopo modifiche ai JSON in `configuration` o `gamedata`, basta refresh pagina.
## 9. Spegnere temporaneamente secure
Per debug rapido, cambia solo `client-mode.json`:
```json
{
"distObfuscationEnabled": false,
"secureAssetsEnabled": false,
"secureApiEnabled": false,
"apiBaseUrl": "https://nitro.example.com:2096",
"plainConfigBaseUrl": "https://hotel.example.com/configuration/",
"plainGamedataBaseUrl": "https://hotel.example.com/client/nitro/gamedata/"
}
```
Poi fai hard refresh.
## 10. `configuration/bootstrap.js`
File:
```txt
Nitro-V3/dist/configuration/bootstrap.js
```
Questo è il primo loader quando usi la modalità secure esterna.
Fa tre cose:
1. apre una sessione ECDH con l'emulatore tramite `/nitro-sec/bootstrap`;
2. scarica `client-mode.json` cifrato da `/nitro-sec/file?kind=config`;
3. scarica `asset-loader.js` cifrato e lo importa come modulo JavaScript.
### Valore da controllare
Dentro `bootstrap.js` esiste:
```js
const API_BASE = "https://nitro.example.com:2096";
```
Deve puntare all'emulatore/API pubblico.
In produzione:
```js
const API_BASE = "https://nitro.example.com:2096";
```
In locale:
```js
const API_BASE = "http://localhost:2096";
```
Se `bootstrap.js` fallisce, prova automaticamente fallback plain su:
```txt
configuration/asset-loader.js
```
Quindi `asset-loader.js` deve esistere sempre nella cartella `configuration`.
## 11. `configuration/asset-loader.js`
File:
```txt
Nitro-V3/dist/configuration/asset-loader.js
```
Questo loader carica il bundle vero:
- se `distObfuscationEnabled=true`
- carica `app.css.dat`;
- carica `app.js.dat`;
- decodifica, decomprime e importa il bundle da blob.
- se `distObfuscationEnabled=false`
- carica `assets/app.css`;
- carica `assets/app.js`.
### File richiesti in produzione
Con offuscamento attivo devono esistere:
```txt
assets/app.css.dat
assets/app.js.dat
configuration/asset-loader.js
configuration/bootstrap.js
configuration/client-mode.json
```
Con offuscamento spento devono esistere:
```txt
assets/app.css
assets/app.js
configuration/asset-loader.js
configuration/client-mode.json
```
## 12. `index.html`
Il file `index.html` deve rimanere minimale.
Esempio secure:
```html
<div id="root"></div>
<script src="/configuration/bootstrap.js"></script>
```
Esempio dev Vite:
```html
<div id="root"></div>
<script type="module" src="/src/bootstrap.ts"></script>
```
Non mischiare i due flow:
- produzione buildata: usa `configuration/bootstrap.js`;
- sviluppo con `yarn start`: usa `/src/bootstrap.ts`.
## 13. File dentro `/configuration`
La cartella `configuration` deve contenere:
```txt
asset-loader.js
bootstrap.js
client-mode.json
renderer-config.json
ui-config.json
adsense.json opzionale
hotlooks.json se usi register hot looks
UITexts.json se usi testi UI separati
```
Le news login non devono stare in `news.json` in produzione: arrivano dal database tramite:
```json
"login.news.url": "${api.url}/api/auth/news"
```
L'emulatore legge dalla tabella `ui_news`.
Con `secureAssetsEnabled=true`, i file letti dal client passano da:
```txt
https://nitro.example.com:2096/nitro-sec/file?kind=config&file=...
```
Quindi l'emulatore li legge da:
```ini
nitro.secure.config.root=C:/inetpub/wwwroot/hotel/nitro/configuration
```
Se aggiungi nuovi file JSON/JS in `configuration` e vuoi proteggerli, devono essere richiesti passando dal secure endpoint o caricati tramite `bootstrap.js`.
+12
View File
@@ -0,0 +1,12 @@
{
"news": [
{
"id": 1,
"title": "Welcome to Nitro",
"body": "This news entry is loaded from configuration/news.json.",
"image": "${image.library.url}web_promo_small/spromo_Canal_Bundle.png",
"link": "",
"linkText": "Read more"
}
]
}
+89 -51
View File
@@ -1,53 +1,91 @@
{ {
"socket.url": "wss://nitro.example.com:2096", "socket.url": "wss://nitro.example.com:2096",
"api.url": "https://nitro.example.com:2096", "api.url": "https://nitro.example.com:2096",
"asset.url": "https://hotel.example.com/client/nitro/bundled", "asset.url": "https://hotel.example.com/client/nitro/bundled",
"image.library.url": "https://hotel.example.com/client/c_images/", "image.library.url": "https://hotel.example.com/client/c_images/",
"hof.furni.url": "https://hotel.example.com/client/c_images/dcr/hof_furni", "hof.furni.url": "https://hotel.example.com/client/c_images/dcr/hof_furni",
"images.url": "https://hotel.example.com/client/nitro/images", "images.url": "https://hotel.example.com/client/nitro/images",
"gamedata.url": "https://nitro.example.com:2096/nitro-sec/file?kind=gamedata&file=", "gamedata.url": "https://nitro.example.com:2096/nitro-sec/file?kind=gamedata&file=",
"sounds.url": "${asset.url}/sounds/%sample%.mp3", "sounds.url": "${asset.url}/sounds/%sample%.mp3",
"external.texts.url": [ "external.texts.url": [
"${gamedata.url}/ExternalTexts.json", "${gamedata.url}/ExternalTexts.json",
"${gamedata.url}/UITexts.json" "${gamedata.url}/UITexts.json"
], ],
"external.texts.translation.url": "${gamedata.url}/text_translate/ExternalTexts_%locale%.json?t=%timestamp%", "external.texts.translation.url": "${gamedata.url}/text_translate/ExternalTexts_%locale%.json?t=%timestamp%",
"external.samples.url": "${hof.furni.url}/mp3/sound_machine_sample_%sample%.mp3", "external.samples.url": "${hof.furni.url}/mp3/sound_machine_sample_%sample%.mp3",
"furnidata.url": "${gamedata.url}/FurnitureData.json?t=%timestamp%", "furnidata.url": "${gamedata.url}/FurnitureData.json?t=%timestamp%",
"furnidata.translation.url": "${gamedata.url}/furniture_translate/FurnitureData_%locale%.json?t=%timestamp%", "furnidata.translation.url": "${gamedata.url}/furniture_translate/FurnitureData_%locale%.json?t=%timestamp%",
"productdata.url": "${gamedata.url}/ProductData.json?t=%timestamp%", "productdata.url": "${gamedata.url}/ProductData.json?t=%timestamp%",
"avatar.actions.url": "${gamedata.url}/HabboAvatarActions.json?t=%timestamp%", "avatar.actions.url": "${gamedata.url}/HabboAvatarActions.json?t=%timestamp%",
"avatar.figuredata.url": "${gamedata.url}/FigureData.json?t=%timestamp%", "avatar.figuredata.url": "${gamedata.url}/FigureData.json?t=%timestamp%",
"avatar.figuremap.url": "${gamedata.url}/FigureMap.json?t=%timestamp%", "avatar.figuremap.url": "${gamedata.url}/FigureMap.json?t=%timestamp%",
"avatar.effectmap.url": "${gamedata.url}/EffectMap.json?t=%timestamp%", "avatar.effectmap.url": "${gamedata.url}/EffectMap.json?t=%timestamp%",
"avatar.asset.url": "${asset.url}/figure/%libname%.nitro", "avatar.asset.url": "${asset.url}/figure/%libname%.nitro",
"avatar.asset.effect.url": "${asset.url}/effect/%libname%.nitro", "avatar.asset.effect.url": "${asset.url}/effect/%libname%.nitro",
"furni.asset.url": "${asset.url}/furniture/%libname%.nitro", "furni.asset.url": "${asset.url}/furniture/%libname%.nitro",
"furni.asset.icon.url": "${hof.furni.url}/icons/%libname%%param%_icon.png", "furni.asset.icon.url": "${hof.furni.url}/icons/%libname%%param%_icon.png",
"pet.asset.url": "${asset.url}/pets/%libname%.nitro", "pet.asset.url": "${asset.url}/pets/%libname%.nitro",
"generic.asset.url": "${asset.url}/generic/%libname%.nitro", "generic.asset.url": "${asset.url}/generic/%libname%.nitro",
"badge.asset.url": "${image.library.url}album1584/%badgename%.gif", "badge.asset.url": "${image.library.url}album1584/%badgename%.gif",
"furni.rotation.bounce.steps": 20, "furni.rotation.bounce.steps": 20,
"furni.rotation.bounce.height": 0.0625, "furni.rotation.bounce.height": 0.0625,
"enable.avatar.arrow": false, "enable.avatar.arrow": false,
"system.log.debug": true, "system.log.debug": true,
"system.log.warn": true, "system.log.warn": true,
"system.log.error": true, "system.log.error": true,
"system.log.events": false, "system.log.events": false,
"system.log.packets": true, "system.log.packets": true,
"system.fps.animation": 24, "system.fps.animation": 24,
"system.fps.max": 60, "system.fps.max": 60,
"system.pong.manually": true, "system.pong.manually": true,
"system.pong.interval.ms": 20000, "system.pong.interval.ms": 20000,
"room.color.skip.transition": true, "room.color.skip.transition": true,
"room.landscapes.enabled": true, "room.landscapes.enabled": true,
"room.zoom.enabled": true, "room.zoom.enabled": true,
"login.screen.enabled": true, "login.screen.enabled": true,
"login.endpoint": "${api.url}/api/auth/login", "login.endpoint": "${api.url}/api/auth/login",
"login.register.endpoint": "${api.url}/api/auth/register", "login.register.endpoint": "${api.url}/api/auth/register",
"login.forgot.endpoint": "${api.url}/api/auth/forgot-password", "login.forgot.endpoint": "${api.url}/api/auth/forgot-password",
"login.logout.endpoint": "${api.url}/api/auth/logout", "login.logout.endpoint": "${api.url}/api/auth/logout",
"login.remember.endpoint": "${api.url}/api/auth/remember", "login.remember.endpoint": "${api.url}/api/auth/remember",
"login.turnstile.enabled": false, "login.turnstile.enabled": false,
"login.turnstile.sitekey": "" "login.turnstile.sitekey": "",
"avatar.mandatory.libraries": [
"bd:1",
"li:0"
],
"avatar.mandatory.effect.libraries": [
"dance.1",
"dance.2",
"dance.3",
"dance.4"
],
"avatar.default.figuredata": {
"palettes": [],
"setTypes": []
},
"avatar.default.actions": {
"actions": []
},
"pet.types": [],
"preload.assets.urls": [
"${asset.url}/generic/avatar_additions.nitro",
"${asset.url}/generic/group_badge.nitro",
"${asset.url}/generic/floor_editor.nitro",
"${images.url}/loading_icon.png",
"${images.url}/clear_icon.png",
"${images.url}/big_arrow.png"
],
"login.health.endpoint": "${api.url}/api/health",
"login.health.method": "GET",
"login.check-email.endpoint": "${api.url}/api/auth/check-email",
"login.check-username.endpoint": "${api.url}/api/auth/check-username",
"login.register.imaging.url": "${api.url}/api/avatar/imaging",
"crypto.ws.enabled": true,
"login.news.url": "${api.url}/api/auth/news",
"badges.custom.list.endpoint": "${api.url}/api/badges/custom",
"badges.custom.create.endpoint": "${api.url}/api/badges/custom",
"badges.custom.update.endpoint": "${api.url}/api/badges/custom/%badgeId%",
"badges.custom.delete.endpoint": "${api.url}/api/badges/custom/%badgeId%",
"badges.custom.texts.endpoint": "${api.url}/api/badges/custom/texts"
} }
File diff suppressed because it is too large Load Diff
+24
View File
@@ -16,6 +16,30 @@ const setBootDebug = (message: string) =>
setBootDebug('boot: secure fetch installed'); setBootDebug('boot: secure fetch installed');
const loadClientMode = async () =>
{
try
{
if((window as any).__nitroClientMode) return;
const url = new URL('configuration/client-mode.json', `${ window.location.origin }/`);
url.searchParams.set('v', Date.now().toString(36));
const response = await fetch(url.toString());
if(!response.ok) throw new Error(`HTTP ${ response.status }`);
(window as any).__nitroClientMode = await response.json();
setBootDebug('boot: client-mode loaded');
}
catch(error)
{
setBootDebug(`boot: client-mode fallback ${ error?.message || error }`);
}
};
await loadClientMode();
const search = new URLSearchParams(window.location.search); const search = new URLSearchParams(window.location.search);
const clientMode = getClientMode(); const clientMode = getClientMode();
const cacheBustUrl = (path: string): string => const cacheBustUrl = (path: string): string =>
+4
View File
@@ -13,6 +13,7 @@ import flagNl from '../../assets/images/flag_icon/flag_icon_nl.png';
import flagSelected from '../../assets/images/flag_icon/flag_icon_selected.png'; import flagSelected from '../../assets/images/flag_icon/flag_icon_selected.png';
import flagTr from '../../assets/images/flag_icon/flag_icon_tr.png'; import flagTr from '../../assets/images/flag_icon/flag_icon_tr.png';
import { applyTextTranslationLocale } from '../../hooks/translation/useTranslation'; import { applyTextTranslationLocale } from '../../hooks/translation/useTranslation';
import { NewsWindow } from './components/NewsWindow';
import { TurnstileWidget } from './TurnstileWidget'; import { TurnstileWidget } from './TurnstileWidget';
type DialogMode = 'login' | 'register' | 'forgot'; type DialogMode = 'login' | 'register' | 'forgot';
@@ -229,6 +230,7 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
const loginUrl = GetConfigurationValue<string>('login.endpoint', '/api/auth/login'); const loginUrl = GetConfigurationValue<string>('login.endpoint', '/api/auth/login');
const registerUrl = GetConfigurationValue<string>('login.register.endpoint', '/api/auth/register'); const registerUrl = GetConfigurationValue<string>('login.register.endpoint', '/api/auth/register');
const forgotUrl = GetConfigurationValue<string>('login.forgot.endpoint', '/api/auth/forgot-password'); const forgotUrl = GetConfigurationValue<string>('login.forgot.endpoint', '/api/auth/forgot-password');
const newsUrl = interpolate(GetConfigurationValue<string>('login.news.url', ''));
const turnstileSiteKey = GetConfigurationValue<string>('login.turnstile.sitekey', ''); const turnstileSiteKey = GetConfigurationValue<string>('login.turnstile.sitekey', '');
const rawTurnstileEnabled = GetConfigurationValue<unknown>('login.turnstile.enabled', false); const rawTurnstileEnabled = GetConfigurationValue<unknown>('login.turnstile.enabled', false);
const turnstileEnabled = (rawTurnstileEnabled === true const turnstileEnabled = (rawTurnstileEnabled === true
@@ -678,6 +680,8 @@ export const LoginView: FC<LoginViewProps> = ({ onAuthenticated, isEntering = fa
}) } }) }
</div> } </div> }
{ newsUrl && <NewsWindow newsUrl={ newsUrl } /> }
<div className="login-stack"> <div className="login-stack">
<div className="nitro-login-card login-language-card"> <div className="nitro-login-card login-language-card">
<div className="card-title">Choose your language</div> <div className="card-title">Choose your language</div>