🆙 Security Fix - Will not go into details

This commit is contained in:
duckietm
2026-03-18 16:52:32 +01:00
parent 6d768c92b1
commit bffaccf6a3
18 changed files with 194 additions and 28 deletions
@@ -7,6 +7,8 @@ import { CatalogEvent, CatalogInitGiftEvent, CatalogPurchasedEvent } from '../..
import { useCatalog, useFriends, useMessageEvent, useUiEvent } from '../../../../hooks';
import { classNames } from '../../../../layout';
let isBuyingGift = false;
export const CatalogGiftView: FC<{}> = props =>
{
const [ isVisible, setIsVisible ] = useState<boolean>(false);
@@ -32,6 +34,7 @@ export const CatalogGiftView: FC<{}> = props =>
const onClose = useCallback(() =>
{
isBuyingGift = false;
setIsVisible(false);
setPageId(0);
setOfferId(0);
@@ -122,6 +125,10 @@ export const CatalogGiftView: FC<{}> = props =>
return;
}
if(isBuyingGift) return;
isBuyingGift = true;
SendMessageComposer(new PurchaseFromCatalogAsGiftComposer(pageId, offerId, extraData, receiverName, message, colourId, selectedBoxIndex, selectedRibbonIndex, showMyFace));
return;
}
@@ -136,6 +143,7 @@ export const CatalogGiftView: FC<{}> = props =>
switch(event.type)
{
case CatalogPurchasedEvent.PURCHASE_SUCCESS:
isBuyingGift = false;
onClose();
return;
case CatalogEvent.INIT_GIFT:
@@ -6,6 +6,8 @@ import { useCatalog, useMessageEvent, useNavigator, useRoomPromote } from '../..
import { NitroInput } from '../../../../../layout';
import { CatalogLayoutProps } from './CatalogLayout.types';
let isPurchasingAd = false;
export const CatalogLayoutRoomAdsView: FC<CatalogLayoutProps> = props =>
{
const { page = null } = props;
@@ -45,6 +47,10 @@ export const CatalogLayoutRoomAdsView: FC<CatalogLayoutProps> = props =>
const purchaseAd = () =>
{
if(isPurchasingAd) return;
isPurchasingAd = true;
const pageId = page.pageId;
const offerId = page.offers.length >= 1 ? page.offers[0].offerId : -1;
const flatId = roomId;
@@ -1,5 +1,5 @@
import { ClubOfferData, GetClubOffersMessageComposer, PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { CatalogPurchaseState, LocalizeText, SendMessageComposer } from '../../../../../api';
import { AutoGrid, Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, LayoutLoadingSpinnerView, Text } from '../../../../../common';
import { CatalogEvent, CatalogPurchaseFailureEvent, CatalogPurchasedEvent } from '../../../../../events';
@@ -13,15 +13,18 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
const { currentPage = null, catalogOptions = null } = useCatalog();
const { purse = null, getCurrencyAmount = null } = usePurse();
const { clubOffers = null } = catalogOptions;
const isPurchasingRef = useRef<boolean>(false);
const onCatalogEvent = useCallback((event: CatalogEvent) =>
{
switch(event.type)
{
case CatalogPurchasedEvent.PURCHASE_SUCCESS:
isPurchasingRef.current = false;
setPurchaseState(CatalogPurchaseState.NONE);
return;
case CatalogPurchaseFailureEvent.PURCHASE_FAILED:
isPurchasingRef.current = false;
setPurchaseState(CatalogPurchaseState.FAILED);
return;
}
@@ -83,8 +86,9 @@ export const CatalogLayoutVipBuyView: FC<CatalogLayoutProps> = props =>
const purchaseSubscription = useCallback(() =>
{
if(!pendingOffer) return;
if(!pendingOffer || isPurchasingRef.current) return;
isPurchasingRef.current = true;
setPurchaseState(CatalogPurchaseState.PURCHASE);
SendMessageComposer(new PurchaseFromCatalogComposer(currentPage.pageId, pendingOffer.offerId, null, 1));
}, [ pendingOffer, currentPage ]);
@@ -1,5 +1,5 @@
import { CancelMarketplaceOfferMessageComposer, GetMarketplaceOwnOffersMessageComposer, MarketplaceCancelOfferResultEvent, MarketplaceOwnOffersEvent, RedeemMarketplaceOfferCreditsMessageComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { LocalizeText, MarketplaceOfferData, MarketPlaceOfferState, NotificationAlertType, SendMessageComposer } from '../../../../../../api';
import { Button, Column, Text } from '../../../../../../common';
import { useMessageEvent, useNotification } from '../../../../../../hooks';
@@ -11,6 +11,8 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC<CatalogLayoutProps> = prop
const [ creditsWaiting, setCreditsWaiting ] = useState(0);
const [ offers, setOffers ] = useState<MarketplaceOfferData[]>([]);
const { simpleAlert = null } = useNotification();
const isRedeemingRef = useRef<boolean>(false);
const pendingCancelsRef = useRef<Set<number>>(new Set());
useMessageEvent<MarketplaceOwnOffersEvent>(MarketplaceOwnOffersEvent, event =>
{
@@ -54,6 +56,10 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC<CatalogLayoutProps> = prop
const redeemSoldOffers = useCallback(() =>
{
if(isRedeemingRef.current) return;
isRedeemingRef.current = true;
setOffers(prevValue =>
{
const idsToDelete = soldOffers.map(value => value.offerId);
@@ -62,11 +68,19 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC<CatalogLayoutProps> = prop
});
SendMessageComposer(new RedeemMarketplaceOfferCreditsMessageComposer());
setTimeout(() => isRedeemingRef.current = false, 3000);
}, [ soldOffers ]);
const takeItemBack = (offerData: MarketplaceOfferData) =>
{
if(pendingCancelsRef.current.has(offerData.offerId)) return;
pendingCancelsRef.current.add(offerData.offerId);
SendMessageComposer(new CancelMarketplaceOfferMessageComposer(offerData.offerId));
setTimeout(() => pendingCancelsRef.current.delete(offerData.offerId), 2000);
};
useEffect(() =>
@@ -1,5 +1,5 @@
import { BuyMarketplaceOfferMessageComposer, GetMarketplaceOffersMessageComposer, MarketplaceBuyOfferResultEvent, MarketPlaceOffersEvent } from '@nitrots/nitro-renderer';
import { FC, useCallback, useMemo, useState } from 'react';
import { FC, useCallback, useMemo, useRef, useState } from 'react';
import { IMarketplaceSearchOptions, LocalizeText, MarketplaceOfferData, MarketplaceSearchType, NotificationAlertType, SendMessageComposer } from '../../../../../../api';
import { Button, Column, Text } from '../../../../../../common';
import { useMessageEvent, useNotification, usePurse } from '../../../../../../hooks';
@@ -23,6 +23,7 @@ export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplac
const [ lastSearch, setLastSearch ] = useState<IMarketplaceSearchOptions>({ minPrice: -1, maxPrice: -1, query: '', type: 3 });
const { getCurrencyAmount = null } = usePurse();
const { simpleAlert = null, showConfirm = null } = useNotification();
const isBuyingRef = useRef<boolean>(false);
const requestOffers = useCallback((options: IMarketplaceSearchOptions) =>
{
@@ -56,6 +57,9 @@ export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplac
showConfirm(LocalizeText('catalog.marketplace.confirm_header'), () =>
{
if(isBuyingRef.current) return;
isBuyingRef.current = true;
SendMessageComposer(new BuyMarketplaceOfferMessageComposer(offerId));
},
null, null, null, LocalizeText('catalog.marketplace.confirm_title'));
@@ -83,6 +87,8 @@ export const CatalogLayoutMarketplacePublicItemsView: FC<CatalogLayoutMarketplac
{
const parser = event.getParser();
isBuyingRef.current = false;
if(!parser) return;
switch(parser.result)
@@ -6,6 +6,8 @@ import { CatalogPostMarketplaceOfferEvent } from '../../../../../../events';
import { useCatalog, useMessageEvent, useNotification, useUiEvent } from '../../../../../../hooks';
import { NitroInput } from '../../../../../../layout';
let isPostingMarketplaceOffer = false;
export const MarketplacePostOfferView: FC<{}> = props =>
{
const [ item, setItem ] = useState<FurnitureItem>(null);
@@ -65,10 +67,15 @@ export const MarketplacePostOfferView: FC<{}> = props =>
const postItem = () =>
{
if(!item || (askingPrice < marketplaceConfiguration.minimumPrice)) return;
if(!item || (askingPrice < marketplaceConfiguration.minimumPrice) || isPostingMarketplaceOffer) return;
showConfirm(LocalizeText('inventory.marketplace.confirm_offer.info', [ 'furniname', 'price' ], [ getFurniTitle, askingPrice.toString() ]), () =>
{
if(isPostingMarketplaceOffer) return;
isPostingMarketplaceOffer = true;
setTimeout(() => isPostingMarketplaceOffer = false, 5000);
SendMessageComposer(new MakeOfferMessageComposer(askingPrice, item.isWallItem ? 2 : 1, item.id));
setItem(null);
},
@@ -6,6 +6,8 @@ import { useCatalog, useNotification, usePurse } from '../../../../../../hooks';
import { CatalogLayoutProps } from '../CatalogLayout.types';
import { VipGiftItem } from './VipGiftItemView';
let isSelectingGift = false;
export const CatalogLayoutVipGiftsView: FC<CatalogLayoutProps> = props =>
{
const { purse = null } = usePurse();
@@ -30,6 +32,10 @@ export const CatalogLayoutVipGiftsView: FC<CatalogLayoutProps> = props =>
{
showConfirm(LocalizeText('catalog.club_gift.confirm'), () =>
{
if(isSelectingGift) return;
isSelectingGift = true;
SendMessageComposer(new SelectClubGiftComposer(localizationId));
setCatalogOptions(prevValue =>
@@ -38,6 +44,8 @@ export const CatalogLayoutVipGiftsView: FC<CatalogLayoutProps> = props =>
return { ...prevValue };
});
setTimeout(() => isSelectingGift = false, 5000);
}, null);
}, [ setCatalogOptions, showConfirm ]);
@@ -11,6 +11,8 @@ interface CatalogPurchaseWidgetViewProps
purchaseCallback?: () => void;
}
let isPurchasingCatalogItem = false;
export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = props =>
{
const { noGiftOption = false, purchaseCallback = null } = props;
@@ -25,15 +27,19 @@ export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = pro
switch(event.type)
{
case CatalogPurchasedEvent.PURCHASE_SUCCESS:
isPurchasingCatalogItem = false;
setPurchaseState(CatalogPurchaseState.NONE);
return;
case CatalogPurchaseFailureEvent.PURCHASE_FAILED:
isPurchasingCatalogItem = false;
setPurchaseState(CatalogPurchaseState.FAILED);
return;
case CatalogPurchaseNotAllowedEvent.NOT_ALLOWED:
isPurchasingCatalogItem = false;
setPurchaseState(CatalogPurchaseState.FAILED);
return;
case CatalogPurchaseSoldOutEvent.SOLD_OUT:
isPurchasingCatalogItem = false;
setPurchaseState(CatalogPurchaseState.SOLD_OUT);
return;
}
@@ -62,7 +68,7 @@ export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = pro
const purchase = (isGift: boolean = false) =>
{
if(!currentOffer) return;
if(!currentOffer || isPurchasingCatalogItem) return;
if(GetClubMemberLevel() < currentOffer.clubLevel)
{
@@ -78,6 +84,7 @@ export const CatalogPurchaseWidgetView: FC<CatalogPurchaseWidgetViewProps> = pro
return;
}
isPurchasingCatalogItem = true;
setPurchaseState(CatalogPurchaseState.PURCHASE);
if(purchaseCallback)
@@ -4,6 +4,8 @@ import { FriendlyTime, GetConfigurationValue, LocalizeText, SendMessageComposer
import { Button, Column, Flex, LayoutCurrencyIcon, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common';
import { usePurse } from '../../../../hooks';
let isBuyingOffer = false;
export const OfferWindowView = (props: { offer: TargetedOfferData, setOpen: Dispatch<SetStateAction<boolean>> }) =>
{
const { offer = null, setOpen = null } = props;
@@ -37,8 +39,14 @@ export const OfferWindowView = (props: { offer: TargetedOfferData, setOpen: Disp
const buyOffer = () =>
{
if(isBuyingOffer) return;
isBuyingOffer = true;
SendMessageComposer(new PurchaseTargetedOfferComposer(offer.id, amount));
SendMessageComposer(new GetTargetedOfferComposer());
setTimeout(() => isBuyingOffer = false, 5000);
};
if(!offer) return;