mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-20 07:26:19 +00:00
Merge duckie main into live merge branch
This commit is contained in:
+47
-1
@@ -57,6 +57,7 @@ export const App: FC<{}> = props =>
|
|||||||
const rendererPromiseRef = useRef<Promise<any>>(null);
|
const rendererPromiseRef = useRef<Promise<any>>(null);
|
||||||
const tickersStartedRef = useRef(false);
|
const tickersStartedRef = useRef(false);
|
||||||
const heartbeatIntervalRef = useRef<number>(null);
|
const heartbeatIntervalRef = useRef<number>(null);
|
||||||
|
const rememberRotateIntervalRef = useRef<number>(null);
|
||||||
const showSessionExpired = useCallback(() =>
|
const showSessionExpired = useCallback(() =>
|
||||||
{
|
{
|
||||||
const baseUrl = window.location.origin + '/';
|
const baseUrl = window.location.origin + '/';
|
||||||
@@ -135,6 +136,45 @@ export const App: FC<{}> = props =>
|
|||||||
return '';
|
return '';
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const rotateRememberLogin = useCallback(async (): Promise<void> =>
|
||||||
|
{
|
||||||
|
const remembered = GetRememberLogin();
|
||||||
|
|
||||||
|
if(!remembered?.token?.length) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const rawEndpoint = GetConfiguration().getValue<string>('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<string, unknown> = {};
|
||||||
|
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)
|
// Listen for socket closed events (code 1000 "Bye" - server rejected SSO)
|
||||||
useNitroEvent(NitroEventType.SOCKET_CLOSED, showSessionExpired);
|
useNitroEvent(NitroEventType.SOCKET_CLOSED, showSessionExpired);
|
||||||
|
|
||||||
@@ -305,6 +345,11 @@ export const App: FC<{}> = props =>
|
|||||||
if(heartbeatIntervalRef.current !== null) window.clearInterval(heartbeatIntervalRef.current);
|
if(heartbeatIntervalRef.current !== null) window.clearInterval(heartbeatIntervalRef.current);
|
||||||
heartbeatIntervalRef.current = window.setInterval(() => HabboWebTools.sendHeartBeat(), 10000);
|
heartbeatIntervalRef.current = window.setInterval(() => HabboWebTools.sendHeartBeat(), 10000);
|
||||||
|
|
||||||
|
if(rememberRotateIntervalRef.current !== null) window.clearInterval(rememberRotateIntervalRef.current);
|
||||||
|
|
||||||
|
const rotateMinutes = Math.max(1, Number(GetConfiguration().getValue<unknown>('login.remember.rotate.interval.minutes', 15)) || 15);
|
||||||
|
if(GetRememberLogin()?.token?.length) rememberRotateIntervalRef.current = window.setInterval(() => rotateRememberLogin(), rotateMinutes * 60 * 1000);
|
||||||
|
|
||||||
if(!tickersStartedRef.current)
|
if(!tickersStartedRef.current)
|
||||||
{
|
{
|
||||||
tickersStartedRef.current = true;
|
tickersStartedRef.current = true;
|
||||||
@@ -330,8 +375,9 @@ export const App: FC<{}> = props =>
|
|||||||
return () =>
|
return () =>
|
||||||
{
|
{
|
||||||
if(heartbeatIntervalRef.current !== null) window.clearInterval(heartbeatIntervalRef.current);
|
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 (
|
return (
|
||||||
<Base fit overflow="hidden" className={ !(window.devicePixelRatio % 1) && 'image-rendering-pixelated' }>
|
<Base fit overflow="hidden" className={ !(window.devicePixelRatio % 1) && 'image-rendering-pixelated' }>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface RememberLoginData
|
|||||||
}
|
}
|
||||||
|
|
||||||
const REMEMBER_LOGIN_KEY = 'nitro.auth.remember';
|
const REMEMBER_LOGIN_KEY = 'nitro.auth.remember';
|
||||||
|
const LEGACY_REMEMBER_LOGIN_KEY = 'nitro.remember.token';
|
||||||
const DEFAULT_REMEMBER_SECONDS = 30 * 24 * 60 * 60;
|
const DEFAULT_REMEMBER_SECONDS = 30 * 24 * 60 * 60;
|
||||||
|
|
||||||
export const GetRememberLogin = (): RememberLoginData | null =>
|
export const GetRememberLogin = (): RememberLoginData | null =>
|
||||||
@@ -26,7 +27,25 @@ export const GetRememberLogin = (): RememberLoginData | null =>
|
|||||||
}
|
}
|
||||||
catch
|
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 =>
|
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 {}
|
catch {}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StoreRememberLoginFromPayload = (payload: Record<string, unknown>, username?: string, ssoTicket?: string): void =>
|
export const StoreRememberLoginFromPayload = (payload: Record<string, unknown>, username?: string, ssoTicket?: string): void =>
|
||||||
{
|
{
|
||||||
const token = typeof payload.rememberToken === 'string' ? payload.rememberToken : '';
|
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 parsedExpiresAt = typeof rawExpiresAt === 'number' ? rawExpiresAt : Number(rawExpiresAt || 0);
|
||||||
const expiresAt = (Number.isFinite(parsedExpiresAt) && parsedExpiresAt > 0)
|
const expiresAt = (Number.isFinite(parsedExpiresAt) && parsedExpiresAt > 0)
|
||||||
? parsedExpiresAt
|
? parsedExpiresAt
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
const [ groupName, setGroupName ] = useState<string>(null);
|
const [ groupName, setGroupName ] = useState<string>(null);
|
||||||
const [ isJukeBox, setIsJukeBox ] = useState<boolean>(false);
|
const [ isJukeBox, setIsJukeBox ] = useState<boolean>(false);
|
||||||
const [ isSongDisk, setIsSongDisk ] = useState<boolean>(false);
|
const [ isSongDisk, setIsSongDisk ] = useState<boolean>(false);
|
||||||
|
const [ isBranded, setIsBranded ] = useState<boolean>(false);
|
||||||
const [ songId, setSongId ] = useState<number>(-1);
|
const [ songId, setSongId ] = useState<number>(-1);
|
||||||
const [ songName, setSongName ] = useState<string>('');
|
const [ songName, setSongName ] = useState<string>('');
|
||||||
const [ songCreator, setSongCreator ] = useState<string>('');
|
const [ songCreator, setSongCreator ] = useState<string>('');
|
||||||
@@ -315,6 +316,7 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
setIsJukeBox(furniIsJukebox);
|
setIsJukeBox(furniIsJukebox);
|
||||||
setIsSongDisk(furniIsSongDisk);
|
setIsSongDisk(furniIsSongDisk);
|
||||||
setSongId(furniSongId);
|
setSongId(furniSongId);
|
||||||
|
setIsBranded(!!avatarInfo.extraParam && avatarInfo.extraParam.indexOf(RoomWidgetEnumItemExtradataParameter.BRANDING_OPTIONS) === 0);
|
||||||
|
|
||||||
if(avatarInfo.groupId) SendMessageComposer(new GroupInformationComposer(avatarInfo.groupId, false));
|
if(avatarInfo.groupId) SendMessageComposer(new GroupInformationComposer(avatarInfo.groupId, false));
|
||||||
}, [ roomSession, avatarInfo ]);
|
}, [ roomSession, avatarInfo ]);
|
||||||
@@ -474,22 +476,24 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
</Flex>
|
</Flex>
|
||||||
<hr className="m-0 bg-[#0003] border-0 opacity-[.5] h-px" />
|
<hr className="m-0 bg-[#0003] border-0 opacity-[.5] h-px" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-1">
|
{ !isBranded &&
|
||||||
<Flex gap={ 1 } position="relative">
|
<div className="flex flex-col gap-1">
|
||||||
{ avatarInfo.stuffData.isUnique &&
|
<Flex gap={ 1 } position="relative">
|
||||||
<div className="absolute inset-e-0">
|
{ avatarInfo.stuffData.isUnique &&
|
||||||
<LayoutLimitedEditionCompactPlateView uniqueNumber={ avatarInfo.stuffData.uniqueNumber } uniqueSeries={ avatarInfo.stuffData.uniqueSeries } />
|
<div className="absolute inset-e-0">
|
||||||
</div> }
|
<LayoutLimitedEditionCompactPlateView uniqueNumber={ avatarInfo.stuffData.uniqueNumber } uniqueSeries={ avatarInfo.stuffData.uniqueSeries } />
|
||||||
{ (avatarInfo.stuffData.rarityLevel > -1) &&
|
</div> }
|
||||||
<div className="absolute inset-e-0">
|
{ (avatarInfo.stuffData.rarityLevel > -1) &&
|
||||||
<LayoutRarityLevelView level={ avatarInfo.stuffData.rarityLevel } />
|
<div className="absolute inset-e-0">
|
||||||
</div> }
|
<LayoutRarityLevelView level={ avatarInfo.stuffData.rarityLevel } />
|
||||||
<Flex center fullWidth>
|
</div> }
|
||||||
<LayoutRoomObjectImageView category={ avatarInfo.category } objectId={ avatarInfo.id } roomId={ roomSession.roomId } />
|
<Flex center fullWidth>
|
||||||
|
<LayoutRoomObjectImageView category={ avatarInfo.category } objectId={ avatarInfo.id } roomId={ roomSession.roomId } />
|
||||||
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
<hr className="m-0 bg-[#0003] border-0 opacity-[.5] h-px" />
|
||||||
<hr className="m-0 bg-[#0003] border-0 opacity-[.5] h-px" />
|
</div>
|
||||||
</div>
|
}
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<Text fullWidth small textBreak wrap variant="white">{ avatarInfo.description }</Text>
|
<Text fullWidth small textBreak wrap variant="white">{ avatarInfo.description }</Text>
|
||||||
<hr className="m-0 bg-[#0003] border-0 opacity-[.5] h-px" />
|
<hr className="m-0 bg-[#0003] border-0 opacity-[.5] h-px" />
|
||||||
@@ -681,7 +685,7 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
return (
|
return (
|
||||||
<Flex key={ index } alignItems="center" gap={ 1 }>
|
<Flex key={ index } alignItems="center" gap={ 1 }>
|
||||||
<Text small wrap align="end" className="col-span-4" variant="white">{ key }</Text>
|
<Text small wrap align="end" className="col-span-4" variant="white">{ key }</Text>
|
||||||
<NitroInput type="text" value={ furniValues[index] } onChange={ event => onFurniSettingChange(index, event.target.value) } />
|
<NitroInput type="text" className="text-black" style={ { color: '#000' } } value={ furniValues[index] } onChange={ event => onFurniSettingChange(index, event.target.value) } />
|
||||||
</Flex>);
|
</Flex>);
|
||||||
}) }
|
}) }
|
||||||
</div>
|
</div>
|
||||||
@@ -696,7 +700,7 @@ export const InfoStandWidgetFurniView: FC<InfoStandWidgetFurniViewProps> = props
|
|||||||
return (
|
return (
|
||||||
<Flex key={ index } alignItems="center" gap={ 1 }>
|
<Flex key={ index } alignItems="center" gap={ 1 }>
|
||||||
<Text small wrap align="end" className="col-span-4" variant="white">{ key }</Text>
|
<Text small wrap align="end" className="col-span-4" variant="white">{ key }</Text>
|
||||||
<NitroInput type="text" value={ customValues[index] } onChange={ event => onCustomVariableChange(index, event.target.value) } />
|
<NitroInput type="text" className="text-black" style={ { color: '#000' } } value={ customValues[index] } onChange={ event => onCustomVariableChange(index, event.target.value) } />
|
||||||
</Flex>);
|
</Flex>);
|
||||||
}) }
|
}) }
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user