feat(events,session): add React-friendly subscribe APIs and snapshot getters

Adds backwards-compatible primitives needed to consume the renderer from
React 19 hooks (useSyncExternalStore, use(), TanStack Query) without
re-architecting the event bus.

- EventDispatcher.subscribe(type, cb): () => void — unsubscriber-returning
  wrapper matching the useSyncExternalStore subscribe signature.
- CommunicationManager.subscribeMessage(eventCtor, handler): () => void —
  packet-stream equivalent.
- SessionDataManager.getUserDataSnapshot() and
  RoomSessionManager.getActiveRoomSessionSnapshot() — referentially-stable
  read-only views invalidated through new SESSION_DATA_UPDATED and
  ROOM_SESSION_UPDATED events.

All additive; existing addEventListener/removeEventListener / IRoomSession
APIs are unchanged. Bumps renderer to 2.1.0.
This commit is contained in:
simoleo89
2026-05-10 19:16:32 +02:00
parent 98b03aa0be
commit 87cf47847c
13 changed files with 193 additions and 5 deletions
+66 -2
View File
@@ -1,8 +1,8 @@
import { IFurnitureData, IGroupInformationManager, IMessageComposer, IMessageEvent, IProductData, ISessionDataManager, NoobnessLevelEnum, SecurityLevel } from '@nitrots/api';
import { IFurnitureData, IGroupInformationManager, IMessageComposer, IMessageEvent, IProductData, ISessionDataManager, IUserDataSnapshot, NoobnessLevelEnum, SecurityLevel } from '@nitrots/api';
import { AccountSafetyLockStatusChangeMessageEvent, AccountSafetyLockStatusChangeParser, AvailabilityStatusMessageEvent, ChangeUserNameResultMessageEvent, EmailStatusResultEvent, FigureUpdateEvent, GetCommunication, GetUserTagsComposer, InClientLinkEvent, MysteryBoxKeysEvent, NoobnessLevelMessageEvent, PetRespectComposer, PetScratchFailedMessageEvent, RoomReadyMessageEvent, RoomUnitChatComposer, UserInfoEvent, UserNameChangeMessageEvent, UserPermissionsEvent, UserRespectComposer, UserTagsMessageEvent } from '@nitrots/communication';
import { GetConfiguration } from '@nitrots/configuration';
import { GetLocalizationManager } from '@nitrots/localization';
import { GetEventDispatcher, MysteryBoxKeysUpdateEvent, NitroSettingsEvent, SessionDataPreferencesEvent, UserNameUpdateEvent } from '@nitrots/events';
import { GetEventDispatcher, MysteryBoxKeysUpdateEvent, NitroEvent, NitroEventType, NitroSettingsEvent, SessionDataPreferencesEvent, UserNameUpdateEvent } from '@nitrots/events';
import { CreateLinkEvent, HabboWebTools } from '@nitrots/utils';
import { Texture } from 'pixi.js';
import { GroupInformationManager } from './GroupInformationManager';
@@ -52,11 +52,49 @@ export class SessionDataManager implements ISessionDataManager
private _badgeImageManager: BadgeImageManager = new BadgeImageManager();
private _userDataSnapshot: Readonly<IUserDataSnapshot> | null = null;
constructor()
{
this.resetUserInfo();
}
private invalidateUserDataSnapshot(): void
{
this._userDataSnapshot = null;
GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SESSION_DATA_UPDATED));
}
public getUserDataSnapshot(): Readonly<IUserDataSnapshot>
{
if(this._userDataSnapshot) return this._userDataSnapshot;
this._userDataSnapshot = Object.freeze<IUserDataSnapshot>({
userId: this._userId,
userName: this._name,
figure: this._figure,
gender: this._gender,
realName: this._realName,
respectsReceived: this._respectsReceived,
respectsLeft: this._respectsLeft,
respectsPetLeft: this._respectsPetLeft,
canChangeName: this._canChangeName,
clubLevel: this._clubLevel,
securityLevel: this._securityLevel,
isAmbassador: this._isAmbassador,
isEmailVerified: this._isEmailVerified,
isNoob: (this._noobnessLevel !== NoobnessLevelEnum.OLD_IDENTITY),
isAuthenticHabbo: this._isAuthenticHabbo,
isSystemOpen: this._systemOpen,
isSystemShutdown: this._systemShutdown,
uiFlags: this._uiFlags,
tags: Object.freeze<string[]>([...this._tags]) as ReadonlyArray<string>
});
return this._userDataSnapshot;
}
public async init(): Promise<void>
{
await Promise.all([
@@ -75,6 +113,8 @@ export class SessionDataManager implements ISessionDataManager
this._gender = event.getParser().gender;
HabboWebTools.updateFigure(this._figure);
this.invalidateUserDataSnapshot();
})),
GetCommunication().registerMessageEvent(new UserInfoEvent(this.onUserInfoEvent.bind(this))),
GetCommunication().registerMessageEvent(new UserPermissionsEvent(this.onUserPermissionsEvent.bind(this))),
@@ -98,6 +138,8 @@ export class SessionDataManager implements ISessionDataManager
this._uiFlags = event.flags;
GetEventDispatcher().dispatchEvent(new SessionDataPreferencesEvent(this._uiFlags));
this.invalidateUserDataSnapshot();
};
GetEventDispatcher().addEventListener<NitroSettingsEvent>(NitroSettingsEvent.SETTINGS_UPDATED, this._settingsEventCallback);
@@ -189,6 +231,8 @@ export class SessionDataManager implements ISessionDataManager
this._safetyLocked = userInfo.safetyLocked;
this._ignoredUsersManager.requestIgnoredUsers(userInfo.username);
this.invalidateUserDataSnapshot();
}
private onUserPermissionsEvent(event: UserPermissionsEvent): void
@@ -198,6 +242,8 @@ export class SessionDataManager implements ISessionDataManager
this._clubLevel = event.getParser().clubLevel;
this._securityLevel = event.getParser().securityLevel;
this._isAmbassador = event.getParser().isAmbassador;
this.invalidateUserDataSnapshot();
}
private onAvailabilityStatusMessageEvent(event: AvailabilityStatusMessageEvent): void
@@ -211,6 +257,8 @@ export class SessionDataManager implements ISessionDataManager
this._systemOpen = parser.isOpen;
this._systemShutdown = parser.onShutdown;
this._isAuthenticHabbo = parser.isAuthenticUser;
this.invalidateUserDataSnapshot();
}
private onPetRespectFailed(event: PetScratchFailedMessageEvent): void
@@ -218,6 +266,8 @@ export class SessionDataManager implements ISessionDataManager
if(!event || !event.connection) return;
this._respectsPetLeft++;
this.invalidateUserDataSnapshot();
}
private onChangeNameUpdateEvent(event: ChangeUserNameResultMessageEvent): void
@@ -233,6 +283,8 @@ export class SessionDataManager implements ISessionDataManager
this._canChangeName = false;
GetEventDispatcher().dispatchEvent(new UserNameUpdateEvent(parser.name));
this.invalidateUserDataSnapshot();
}
private onUserNameChangeMessageEvent(event: UserNameChangeMessageEvent): void
@@ -249,6 +301,8 @@ export class SessionDataManager implements ISessionDataManager
this._canChangeName = false;
GetEventDispatcher().dispatchEvent(new UserNameUpdateEvent(this._name));
this.invalidateUserDataSnapshot();
}
private onUserTags(event: UserTagsMessageEvent): void
@@ -260,6 +314,8 @@ export class SessionDataManager implements ISessionDataManager
if(!parser) return;
this._tags = parser.tags;
this.invalidateUserDataSnapshot();
}
private onRoomModelNameEvent(event: RoomReadyMessageEvent): void
@@ -300,6 +356,8 @@ export class SessionDataManager implements ISessionDataManager
this._noobnessLevel = event.getParser().noobnessLevel;
if(this._noobnessLevel !== NoobnessLevelEnum.OLD_IDENTITY) GetConfiguration().setValue<number>('new.identity', 1);
this.invalidateUserDataSnapshot();
}
private onAccountSafetyLockStatusChangeMessageEvent(event: AccountSafetyLockStatusChangeMessageEvent): void
@@ -316,6 +374,8 @@ export class SessionDataManager implements ISessionDataManager
private onEmailStatus(event: EmailStatusResultEvent): void
{
this._isEmailVerified = event?.getParser()?.isVerified ?? false;
this.invalidateUserDataSnapshot();
}
public getFloorItemData(id: number): IFurnitureData
@@ -476,6 +536,8 @@ export class SessionDataManager implements ISessionDataManager
this.send(new UserRespectComposer(userId));
this._respectsLeft--;
this.invalidateUserDataSnapshot();
}
public givePetRespect(petId: number): void
@@ -485,6 +547,8 @@ export class SessionDataManager implements ISessionDataManager
this.send(new PetRespectComposer(petId));
this._respectsPetLeft--;
this.invalidateUserDataSnapshot();
}
public sendSpecialCommandMessage(text: string, styleId: number = 0): void