Merge pull request #99 from duckietm/Dev

Dev
This commit is contained in:
DuckieTM
2026-04-23 10:20:04 +02:00
committed by GitHub
7 changed files with 1349 additions and 149 deletions
+62 -2
View File
@@ -109,5 +109,65 @@
"groupforum.message.hide": "Hide message",
"group.forum.enable.caption": "Enable / Disable group forum",
"group.forum.enable.help": "If you disable the group forum, all posts will also be deleted!",
"groupforum.view.no_threads": "There are currently no active threads"
}
"groupforum.view.no_threads": "There are currently no active threads",
"login.username": "Name of your Habbo",
"login.forgot_password": "Forgotten your password?",
"nitro.login.firsttime.title": "First time here?",
"nitro.login.firsttime.text": "Don't have a Habbo yet?",
"nitro.login.firsttime.link": "You can create one here",
"nitro.login.card.title": "What's your Habbo called?",
"nitro.login.server.offline.short": "The gameserver isn't running right now. Please try again in a moment.",
"nitro.login.server.offline.long": "The gameserver isn't running right now, so new accounts can't be created. Please try again in a moment.",
"nitro.login.server.checking": "Checking…",
"nitro.login.server.retry": "Retry",
"nitro.login.register.title": "Habbo Details",
"nitro.login.register.next": "Next",
"nitro.login.register.finish": "Finish",
"nitro.login.register.creating": "Creating…",
"nitro.login.register.intro.credentials": "Let's create your account. Enter your email and pick a password — we'll check that email isn't already in use.",
"nitro.login.register.intro.avatar": "Now it's time to make your own Habbo character! To make your own Habbo, please start by choosing your Habbo Name.",
"nitro.login.register.intro.room": "Last step — pick a starter room, or skip and create your own later.",
"nitro.login.register.confirm.label": "Confirm password",
"nitro.login.register.username.placeholder": "HabboName",
"nitro.login.register.hotlooks.count": "%count% looks available",
"nitro.login.register.hotlooks.none": "No hot looks loaded",
"nitro.login.register.room.skip.title": "I'm okay — I'll create my own rooms",
"nitro.login.register.room.skip.description": "Skip for now and start with an empty hotel inventory.",
"nitro.login.register.room.loading": "Loading rooms…",
"nitro.login.register.room.error": "Could not load room options. You can still skip this step.",
"nitro.login.register.success": "Welcome aboard, %username%! Your account is ready — log in below with the password you just chose.",
"nitro.login.forgot.title": "Reset password",
"nitro.login.forgot.email.label": "Email address",
"nitro.login.forgot.send": "Send email",
"nitro.login.forgot.success": "Email sent! If an account matches that address you'll find a reset link in your inbox shortly (check spam if it doesn't show up within a minute).",
"nitro.login.error.missing_credentials": "Please enter both your Habbo name and password.",
"nitro.login.error.invalid_credentials": "Invalid Habbo name or password.",
"nitro.login.error.too_many_attempts": "Too many attempts. Try again in %seconds%s.",
"nitro.login.error.turnstile": "Please complete the security check.",
"nitro.login.error.server_offline": "The gameserver is not running. Please try again later.",
"nitro.login.error.login_unreachable": "Unable to reach the login service. Please try again.",
"nitro.login.error.register_failed": "Unable to create your account.",
"nitro.login.error.register_unreachable": "Unable to reach the registration service.",
"nitro.login.error.forgot_failed": "Unable to send a reset email right now.",
"nitro.login.error.forgot_unreachable": "Unable to reach the password reset service.",
"nitro.login.error.missing_fields": "Please fill in every field.",
"nitro.login.error.invalid_email": "Please enter a valid email address.",
"nitro.login.error.password_too_short": "Your password must be at least 8 characters.",
"nitro.login.error.password_mismatch": "Passwords do not match.",
"nitro.login.error.email_taken": "This email is already in use.",
"nitro.login.error.missing_username": "Please choose a Habbo name.",
"nitro.login.error.username_length": "Habbo name must be 316 characters.",
"nitro.login.error.username_taken": "This Habbo name is already taken.",
"nitro.login.error.missing_email": "Please enter your email address."
}
}
+52
View File
@@ -0,0 +1,52 @@
[
{
"_gender": "m",
"_figure": "hr-155-40.hd-180-10.ch-255-1408.lg-280-64.sh-290-64.ha-1003-64",
"_hash": "b5d1a24d16c9d516b3d793c66d152b77"
},
{
"_gender": "f",
"_figure": "hr-515-34.hd-629-8.ch-665-1408.lg-715-1320.sh-740-1408.he-1608",
"_hash": "694573ec86cf5346f1c88b1017f069f8"
},
{
"_gender": "f",
"_figure": "hr-890-36.hd-629-8.ch-685-71.lg-715-71.sh-3068-71-73.ha-1018.fa-1202-71.ca-1802",
"_hash": "10b9a935209e6c213e54108474186dc8"
},
{
"_gender": "m",
"_figure": "hr-115-42.hd-209-1.ch-255-73.lg-3078-82.sh-300-64",
"_hash": "1457ce2369b982bcce30e8307c005d98"
},
{
"_gender": "m",
"_figure": "hr-115-40.hd-190-14.ch-235-1408.lg-280-1408.sh-908-1408.he-1608",
"_hash": "d35b7492386963d7612341b222f7f5d9"
},
{
"_gender": "m",
"_figure": "hr-115-31.hd-180-14.ch-210-64.lg-3023-91.sh-300-91",
"_hash": "b49e529b7604fbd3596951bc69d6551b"
},
{
"_gender": "m",
"_figure": "hr-100.hd-180-1.ch-210-1408.lg-270-64.sh-300-64.ha-1002-64.cc-260-64",
"_hash": "f052b0ccc54cfa933d473b433b154ef5"
},
{
"_gender": "m",
"_figure": "hr-125-34.hd-205-14.ch-235-1408.lg-285-81.sh-300-64.wa-3211-64-64",
"_hash": "08c77292a4462c36f0393820d5753de3"
},
{
"_gender": "f",
"_figure": "hr-890-31.hd-600-1.ch-822-71.lg-715-74.he-1602-71",
"_hash": "4102a76da4bca25d5125b75d9ea1ca14"
},
{
"_gender": "f",
"_figure": "hr-515-35.hd-628-14.ch-667.lg-696-73.he-1606-82.ca-1810.cp-3124-81",
"_hash": "4987ff565ec8e6ecedb31b08c2b017a6"
}
]
@@ -45,6 +45,11 @@
"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.health.endpoint": "${api.url}/api/health",
"login.check-email.endpoint": "${api.url}/api/auth/check-email",
"login.check-username.endpoint": "${api.url}/api/auth/check-username",
"login.room_templates.endpoint": "${api.url}/api/auth/room-templates",
"login.remember.endpoint": "${api.url}/api/auth/remember",
"login.turnstile.enabled": false,
"login.turnstile.sitekey": "",
"avatar.mandatory.libraries": [
+59 -9
View File
@@ -35,7 +35,6 @@ export const App: FC<{}> = props =>
setPrepareTrigger(prev => prev + 1);
}, []);
// Listen for socket closed events (code 1000 "Bye" - server rejected SSO)
useNitroEvent(NitroEventType.SOCKET_CLOSED, showSessionExpired);
useMessageEvent<LoadGameUrlEvent>(LoadGameUrlEvent, event =>
@@ -57,26 +56,77 @@ export const App: FC<{}> = props =>
{
if(!window.NitroConfig) throw new Error('NitroConfig is not defined!');
const ssoTicket = window.NitroConfig['sso.ticket'];
let ssoTicket = window.NitroConfig['sso.ticket'];
let configInitError: unknown = null;
if(!ssoTicket || ssoTicket === '')
{
// Configuration is loaded lazily — fetch it up-front so the login
// screen toggle and Turnstile keys are available before we decide.
let configInitError: unknown = null;
try { await GetConfiguration().init(); }
catch(e) { configInitError = e; }
const rawLoginEnabled = GetConfiguration().getValue<unknown>('login.screen.enabled', false);
const loginScreenEnabled = rawLoginEnabled === true || rawLoginEnabled === 'true' || rawLoginEnabled === 1;
if(configInitError)
{
NitroLogger.error('[LoginScreen] Failed to load renderer-config.json — cannot resolve login.screen.enabled', configInitError);
}
if(!configInitError)
{
let storedRemember: string | null = null;
try { storedRemember = window.localStorage.getItem('nitro.remember.token'); }
catch {}
if(storedRemember)
{
const rememberUrlTemplate = GetConfiguration().getValue<string>('login.remember.endpoint', '/api/auth/remember');
const rememberUrl = GetConfiguration().interpolate(rememberUrlTemplate);
try
{
const response = await fetch(rememberUrl, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Requested-With': 'NitroRememberMe'
},
body: JSON.stringify({ rememberToken: storedRemember })
});
if(response.ok)
{
const payload = await response.json();
const ticket = typeof payload.ssoTicket === 'string' ? payload.ssoTicket : '';
if(ticket)
{
window.NitroConfig['sso.ticket'] = ticket;
ssoTicket = ticket;
try
{
if(typeof payload.rememberToken === 'string' && payload.rememberToken.length)
window.localStorage.setItem('nitro.remember.token', payload.rememberToken);
}
catch {}
}
}
else if(response.status === 401)
{
try { window.localStorage.removeItem('nitro.remember.token'); } catch {}
}
}
catch {}
}
}
}
if(!ssoTicket || ssoTicket === '')
{
const rawLoginEnabled = GetConfiguration().getValue<unknown>('login.screen.enabled', false);
const loginScreenEnabled = rawLoginEnabled === true || rawLoginEnabled === 'true' || rawLoginEnabled === 1;
if(loginScreenEnabled)
{
try { await GetLocalizationManager().init(); }
catch(localizationErr) { NitroLogger.error('[LoginScreen] Localization init failed', localizationErr); }
setIsReady(false);
setShowLogin(true);
return;
@@ -110,7 +160,7 @@ export const App: FC<{}> = props =>
eventMode: 'none',
failIfMajorPerformanceCaveat: false,
roundPixels: true,
useBackBuffer // Keep disabled by default unless explicitly enabled in NitroConfig
useBackBuffer
});
await GetConfiguration().init();
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -64,6 +64,9 @@ export const PurseView: FC<{}> = props => {
const logoutUrl = GetConfigurationValue<string>('login.logout.endpoint', '/api/auth/logout');
const ssoTicket = (window.NitroConfig?.['sso.ticket'] as string) ?? '';
let rememberToken = '';
try { rememberToken = window.localStorage.getItem('nitro.remember.token') ?? ''; }
catch { /* localStorage may be disabled */ }
try
{
@@ -76,11 +79,12 @@ export const PurseView: FC<{}> = props => {
'Accept': 'application/json',
'X-Requested-With': 'NitroPurseLogout'
},
body: JSON.stringify({ ssoTicket })
body: JSON.stringify({ ssoTicket, rememberToken })
});
}
catch { /* best-effort — proceed with local logout regardless */ }
try { window.localStorage.removeItem('nitro.remember.token'); } catch { /* noop */ }
if(window.NitroConfig) window.NitroConfig['sso.ticket'] = '';
window.location.reload();
}, []);
+296 -18
View File
@@ -1,18 +1,3 @@
/* ─── Classic Login View ─────────────────────────────────────────────────
Port of the old Nitro HotelView background layering, used exclusively by
the login screen. Assets are driven by ui-config.json:
loginview.images.background → .login-background
loginview.images.background.colour → .nitro-login-view base colour
loginview.images.sun → .login-sun
loginview.images.drape → .login-drape
loginview.images.left → .login-left
loginview.images.right → .login-right
loginview.images.right.repeat → .login-right-repeat
Class names are deliberately prefixed so HotelView.css rules
(.left { left: 18vw !important } etc.) cannot clobber us.
--------------------------------------------------------------------- */
.nitro-login-view {
position: fixed;
inset: 0;
@@ -82,7 +67,7 @@
background-position: right bottom;
}
/* ─── Foreground Login Card Stack ───────────────────────────────────── */
/* ─── Foreground Login Card Stack ─── */
.nitro-login-view .login-stack {
position: absolute;
@@ -167,6 +152,22 @@
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 0 0 2px rgba(63, 106, 133, 0.3);
}
.nitro-login-card .remember-me {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
color: #0a2e45;
user-select: none;
cursor: pointer;
margin: -2px 0 2px 0;
}
.nitro-login-card .remember-me input[type="checkbox"] {
margin: 0;
cursor: pointer;
}
.nitro-login-card .submit-row {
display: flex;
justify-content: center;
@@ -242,8 +243,6 @@
max-width: 100%;
}
/* Modal overlay used for register + forgot password dialogs */
.nitro-login-modal {
position: fixed;
inset: 0;
@@ -259,3 +258,282 @@
max-width: calc(100% - 40px);
}
.nitro-login-modal .dialog.dialog-avatar {
width: 400px;
}
/* ─── Multi-step register dialog ─── */
.nitro-login-card .register-intro {
background: #eef4f8;
border: 1px solid #b6cfdd;
border-radius: 4px;
padding: 6px 8px;
font-size: 11px;
line-height: 1.4;
color: #0a2e45;
}
.nitro-login-card .step-footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
margin-top: 4px;
}
.nitro-login-card .step-footer-split {
justify-content: space-between;
}
.nitro-login-card .step-indicator {
font-size: 11px;
color: #134b6e;
font-weight: 600;
}
.nitro-login-card .back-button {
background: #d5e2eb;
}
/* ─── Avatar builder (pre-login) ─── */
.nitro-login-card .gender-row {
display: flex;
justify-content: center;
gap: 22px;
font-size: 11px;
font-weight: 600;
}
.nitro-login-card .gender-row label {
display: inline-flex;
align-items: center;
gap: 4px;
cursor: pointer;
}
.nitro-login-card .avatar-builder {
display: grid;
grid-template-columns: 74px 1fr 74px;
gap: 6px;
align-items: stretch;
background: repeating-linear-gradient(
0deg,
#ffffff 0,
#ffffff 8px,
#e5ecf1 8px,
#e5ecf1 16px
);
border: 1px solid #7595ac;
border-radius: 6px;
padding: 6px;
}
.nitro-login-card .avatar-part-col,
.nitro-login-card .avatar-color-col {
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 4px;
}
.nitro-login-card .avatar-part-row,
.nitro-login-card .avatar-color-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 3px;
min-height: 44px;
}
.nitro-login-card .arrow-btn {
width: 16px;
height: 20px;
line-height: 1;
padding: 0;
border: 1px solid #7595ac;
border-radius: 3px;
background: #ffffff;
color: #0a2e45;
font-size: 14px;
font-weight: 700;
cursor: pointer;
box-shadow: inset 0 1px rgba(255, 255, 255, 0.8), 0 1px rgba(0, 0, 0, 0.15);
flex-shrink: 0;
}
.nitro-login-card .arrow-btn:hover {
background: #e9f1f7;
}
.nitro-login-card .part-preview {
flex: 1;
height: 44px;
border: 1px solid #7595ac;
border-radius: 3px;
background: #ffffff;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
box-shadow: inset 0 1px rgba(255, 255, 255, 0.8);
}
.nitro-login-card .part-preview img {
image-rendering: pixelated;
image-rendering: -moz-crisp-edges;
pointer-events: none;
user-select: none;
max-width: none;
height: auto;
}
.nitro-login-card .part-preview-hr img,
.nitro-login-card .part-preview-hd img {
width: 40px;
height: auto;
}
.nitro-login-card .part-preview-ch img {
width: 50px;
margin-top: 8px;
}
.nitro-login-card .part-preview-lg img {
width: 50px;
margin-top: -8px;
}
.nitro-login-card .part-preview-sh img {
width: 50px;
margin-top: -22px;
}
.nitro-login-card .color-swatch {
flex: 1;
height: 18px;
border: 1px solid #7595ac;
border-radius: 3px;
box-shadow: inset 0 1px rgba(255, 255, 255, 0.4);
}
.nitro-login-card .avatar-preview {
display: flex;
align-items: flex-end;
justify-content: center;
min-height: 130px;
overflow: hidden;
}
.nitro-login-card .avatar-preview img {
max-width: 100%;
max-height: 140px;
image-rendering: pixelated;
image-rendering: -moz-crisp-edges;
}
.nitro-login-card .hot-looks-row {
display: flex;
justify-content: center;
margin-top: 2px;
}
.nitro-login-card .server-offline {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 6px;
text-align: left;
}
.nitro-login-card .server-offline .retry-link {
background: #ffffff;
border: 1px solid #3f6a85;
border-radius: 4px;
padding: 2px 10px;
font-size: 11px;
font-weight: 700;
color: #0a2e45;
cursor: pointer;
box-shadow: inset 0 1px rgba(255, 255, 255, 0.8), 0 1px rgba(0, 0, 0, 0.15);
}
.nitro-login-card .server-offline .retry-link:disabled {
opacity: 0.55;
cursor: not-allowed;
}
.nitro-login-card .hot-looks-button {
padding: 4px 14px;
font-size: 11px;
}
/* ─── Room template picker (step 3) ─── */
.nitro-login-card .room-templates-list {
display: flex;
flex-direction: column;
gap: 6px;
max-height: 260px;
overflow-y: auto;
padding-right: 4px;
}
.nitro-login-card .room-template-option {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 8px 10px;
border: 1px solid #b6cfdd;
border-radius: 4px;
background: #eef4f8;
cursor: pointer;
transition: border-color 0.15s, background 0.15s;
}
.nitro-login-card .room-template-option:hover {
border-color: #7fa9c3;
}
.nitro-login-card .room-template-option.selected {
border-color: #2e6b92;
background: #d9e8f2;
}
.nitro-login-card .room-template-option input[type="radio"] {
margin: 2px 0 0 0;
flex-shrink: 0;
cursor: pointer;
}
.nitro-login-card .room-template-thumb {
width: 48px;
height: 48px;
object-fit: cover;
border-radius: 3px;
flex-shrink: 0;
}
.nitro-login-card .room-template-body {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
flex: 1;
}
.nitro-login-card .room-template-title {
font-weight: 700;
font-size: 12px;
color: #0a2e45;
line-height: 1.2;
}
.nitro-login-card .room-template-description {
font-size: 11px;
color: #2a4a5c;
line-height: 1.3;
}