diff --git a/src/App.tsx b/src/App.tsx index 0728274..7722748 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -57,6 +57,7 @@ export const App: FC<{}> = props => const rendererPromiseRef = useRef>(null); const tickersStartedRef = useRef(false); const heartbeatIntervalRef = useRef(null); + const rememberRotateIntervalRef = useRef(null); const showSessionExpired = useCallback(() => { const baseUrl = window.location.origin + '/'; @@ -135,6 +136,45 @@ export const App: FC<{}> = props => return ''; }, []); + const rotateRememberLogin = useCallback(async (): Promise => + { + const remembered = GetRememberLogin(); + + if(!remembered?.token?.length) return; + + try + { + const rawEndpoint = GetConfiguration().getValue('login.refresh.endpoint', '${api.url}/api/auth/refresh'); + const endpoint = GetConfiguration().interpolate(rawEndpoint); + const response = await fetch(endpoint, { + method: 'POST', + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'X-Requested-With': 'NitroRememberRotate' + }, + body: JSON.stringify({ rememberToken: remembered.token }) + }); + + let payload: Record = {}; + try { payload = await response.json(); } + catch {} + + if(response.ok) + { + StoreRememberLoginFromPayload(payload, remembered.username, remembered.ssoTicket); + return; + } + + if(response.status === 400 || response.status === 401 || response.status === 403) ClearRememberLogin(); + } + catch(error) + { + NitroLogger.error('[LoginScreen] Remember rotation failed', error); + } + }, []); + // Listen for socket closed events (code 1000 "Bye" - server rejected SSO) useNitroEvent(NitroEventType.SOCKET_CLOSED, showSessionExpired); @@ -305,6 +345,11 @@ export const App: FC<{}> = props => if(heartbeatIntervalRef.current !== null) window.clearInterval(heartbeatIntervalRef.current); heartbeatIntervalRef.current = window.setInterval(() => HabboWebTools.sendHeartBeat(), 10000); + if(rememberRotateIntervalRef.current !== null) window.clearInterval(rememberRotateIntervalRef.current); + + const rotateMinutes = Math.max(1, Number(GetConfiguration().getValue('login.remember.rotate.interval.minutes', 15)) || 15); + if(GetRememberLogin()?.token?.length) rememberRotateIntervalRef.current = window.setInterval(() => rotateRememberLogin(), rotateMinutes * 60 * 1000); + if(!tickersStartedRef.current) { tickersStartedRef.current = true; @@ -330,8 +375,9 @@ export const App: FC<{}> = props => return () => { if(heartbeatIntervalRef.current !== null) window.clearInterval(heartbeatIntervalRef.current); + if(rememberRotateIntervalRef.current !== null) window.clearInterval(rememberRotateIntervalRef.current); }; - }, [ prepareTrigger, startWarmup, startRenderer, tryRememberLogin, applySsoTicket ]); + }, [ prepareTrigger, startWarmup, startRenderer, tryRememberLogin, applySsoTicket, rotateRememberLogin ]); return ( diff --git a/src/api/utils/RememberLogin.ts b/src/api/utils/RememberLogin.ts index e886126..6609af8 100644 --- a/src/api/utils/RememberLogin.ts +++ b/src/api/utils/RememberLogin.ts @@ -7,6 +7,7 @@ export interface RememberLoginData } const REMEMBER_LOGIN_KEY = 'nitro.auth.remember'; +const LEGACY_REMEMBER_LOGIN_KEY = 'nitro.remember.token'; const DEFAULT_REMEMBER_SECONDS = 30 * 24 * 60 * 60; export const GetRememberLogin = (): RememberLoginData | null => @@ -26,7 +27,25 @@ export const GetRememberLogin = (): RememberLoginData | null => } catch { - return null; + try + { + const legacyToken = window.localStorage.getItem(LEGACY_REMEMBER_LOGIN_KEY) || ''; + + if(!legacyToken.length) return null; + + const data: RememberLoginData = { + token: legacyToken, + expiresAt: Math.floor(Date.now() / 1000) + DEFAULT_REMEMBER_SECONDS + }; + + SetRememberLogin(data); + + return data; + } + catch + { + return null; + } } }; @@ -40,14 +59,18 @@ export const SetRememberLogin = (data: RememberLoginData): void => export const ClearRememberLogin = (): void => { - try { window.localStorage.removeItem(REMEMBER_LOGIN_KEY); } + try + { + window.localStorage.removeItem(REMEMBER_LOGIN_KEY); + window.localStorage.removeItem(LEGACY_REMEMBER_LOGIN_KEY); + } catch {} }; export const StoreRememberLoginFromPayload = (payload: Record, username?: string, ssoTicket?: string): void => { const token = typeof payload.rememberToken === 'string' ? payload.rememberToken : ''; - const rawExpiresAt = payload.rememberExpiresAt; + const rawExpiresAt = (payload.rememberExpiresAt ?? payload.expiresAt); const parsedExpiresAt = typeof rawExpiresAt === 'number' ? rawExpiresAt : Number(rawExpiresAt || 0); const expiresAt = (Number.isFinite(parsedExpiresAt) && parsedExpiresAt > 0) ? parsedExpiresAt diff --git a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetFurniView.tsx b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetFurniView.tsx index 50e3923..d415a1b 100644 --- a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetFurniView.tsx +++ b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetFurniView.tsx @@ -39,6 +39,7 @@ export const InfoStandWidgetFurniView: FC = props const [ groupName, setGroupName ] = useState(null); const [ isJukeBox, setIsJukeBox ] = useState(false); const [ isSongDisk, setIsSongDisk ] = useState(false); + const [ isBranded, setIsBranded ] = useState(false); const [ songId, setSongId ] = useState(-1); const [ songName, setSongName ] = useState(''); const [ songCreator, setSongCreator ] = useState(''); @@ -315,6 +316,7 @@ export const InfoStandWidgetFurniView: FC = props setIsJukeBox(furniIsJukebox); setIsSongDisk(furniIsSongDisk); setSongId(furniSongId); + setIsBranded(!!avatarInfo.extraParam && avatarInfo.extraParam.indexOf(RoomWidgetEnumItemExtradataParameter.BRANDING_OPTIONS) === 0); if(avatarInfo.groupId) SendMessageComposer(new GroupInformationComposer(avatarInfo.groupId, false)); }, [ roomSession, avatarInfo ]); @@ -474,22 +476,24 @@ export const InfoStandWidgetFurniView: FC = props
-
- - { avatarInfo.stuffData.isUnique && -
- -
} - { (avatarInfo.stuffData.rarityLevel > -1) && -
- -
} - - + { !isBranded && +
+ + { avatarInfo.stuffData.isUnique && +
+ +
} + { (avatarInfo.stuffData.rarityLevel > -1) && +
+ +
} + + +
- -
-
+
+
+ }
{ avatarInfo.description }
@@ -681,7 +685,7 @@ export const InfoStandWidgetFurniView: FC = props return ( { key } - onFurniSettingChange(index, event.target.value) } /> + onFurniSettingChange(index, event.target.value) } /> ); }) }
@@ -696,7 +700,7 @@ export const InfoStandWidgetFurniView: FC = props return ( { key } - onCustomVariableChange(index, event.target.value) } /> + onCustomVariableChange(index, event.target.value) } /> ); }) }