From 87cf47847c76fb809e61bffe1acf5444b43df084 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sun, 10 May 2026 19:16:32 +0200 Subject: [PATCH 01/30] feat(events,session): add React-friendly subscribe APIs and snapshot getters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- package.json | 2 +- packages/api/src/common/IEventDispatcher.ts | 1 + .../communication/ICommunicationManager.ts | 1 + .../src/nitro/session/IRoomSessionManager.ts | 2 + .../src/nitro/session/IRoomSessionSnapshot.ts | 18 +++++ .../src/nitro/session/ISessionDataManager.ts | 2 + .../src/nitro/session/IUserDataSnapshot.ts | 22 ++++++ packages/api/src/nitro/session/index.ts | 2 + .../communication/src/CommunicationManager.ts | 11 +++ packages/events/src/EventDispatcher.ts | 19 ++++++ packages/events/src/NitroEventType.ts | 2 + packages/session/src/RoomSessionManager.ts | 48 ++++++++++++- packages/session/src/SessionDataManager.ts | 68 ++++++++++++++++++- 13 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 packages/api/src/nitro/session/IRoomSessionSnapshot.ts create mode 100644 packages/api/src/nitro/session/IUserDataSnapshot.ts diff --git a/package.json b/package.json index ff1499d..ff24624 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@nitrots/nitro-renderer", "description": "Javascript library for rendering Nitro in the browser using PixiJS", - "version": "2.0.0", + "version": "2.1.0", "private": true, "type": "module", "workspaces": [ diff --git a/packages/api/src/common/IEventDispatcher.ts b/packages/api/src/common/IEventDispatcher.ts index 2e4ebdb..9217b0c 100644 --- a/packages/api/src/common/IEventDispatcher.ts +++ b/packages/api/src/common/IEventDispatcher.ts @@ -7,4 +7,5 @@ export interface IEventDispatcher removeEventListener(type: string, callback: Function): void; removeAllListeners(): void; dispatchEvent(event: T): boolean; + subscribe(type: string | string[], callback: (event: T) => void): () => void; } diff --git a/packages/api/src/communication/ICommunicationManager.ts b/packages/api/src/communication/ICommunicationManager.ts index 7c15877..76e17e0 100644 --- a/packages/api/src/communication/ICommunicationManager.ts +++ b/packages/api/src/communication/ICommunicationManager.ts @@ -6,5 +6,6 @@ export interface ICommunicationManager init(): Promise; registerMessageEvent(event: IMessageEvent): IMessageEvent; removeMessageEvent(event: IMessageEvent): void; + subscribeMessage(eventCtor: new (callback: (event: T) => void) => T, handler: (event: T) => void): () => void; connection: IConnection; } diff --git a/packages/api/src/nitro/session/IRoomSessionManager.ts b/packages/api/src/nitro/session/IRoomSessionManager.ts index 974c98a..777345e 100644 --- a/packages/api/src/nitro/session/IRoomSessionManager.ts +++ b/packages/api/src/nitro/session/IRoomSessionManager.ts @@ -1,4 +1,5 @@ import { IRoomSession } from './IRoomSession'; +import { IRoomSessionSnapshot } from './IRoomSessionSnapshot'; export interface IRoomSessionManager { @@ -8,5 +9,6 @@ export interface IRoomSessionManager startSession(session: IRoomSession): boolean; removeSession(id: number, openLandingView?: boolean): void; tryRestoreSession(): boolean; + getActiveRoomSessionSnapshot(): Readonly | null; viewerSession: IRoomSession; } diff --git a/packages/api/src/nitro/session/IRoomSessionSnapshot.ts b/packages/api/src/nitro/session/IRoomSessionSnapshot.ts new file mode 100644 index 0000000..5b1b7e2 --- /dev/null +++ b/packages/api/src/nitro/session/IRoomSessionSnapshot.ts @@ -0,0 +1,18 @@ +import { IRoomSession } from './IRoomSession'; + +export interface IRoomSessionSnapshot +{ + roomId: number; + state: string; + isRoomOwner: boolean; + isSpectator: boolean; + isDecorating: boolean; + isGuildRoom: boolean; + isPrivateRoom: boolean; + controllerLevel: number; + doorMode: number; + tradeMode: number; + allowPets: boolean; + groupId: number; + session: IRoomSession; +} diff --git a/packages/api/src/nitro/session/ISessionDataManager.ts b/packages/api/src/nitro/session/ISessionDataManager.ts index 26947e6..59b0d97 100644 --- a/packages/api/src/nitro/session/ISessionDataManager.ts +++ b/packages/api/src/nitro/session/ISessionDataManager.ts @@ -3,6 +3,7 @@ import { IFurnitureData } from './IFurnitureData'; import { IGroupInformationManager } from './IGroupInformationManager'; import { IIgnoredUsersManager } from './IIgnoredUsersManager'; import { IProductData } from './IProductData'; +import { IUserDataSnapshot } from './IUserDataSnapshot'; export interface ISessionDataManager { @@ -53,4 +54,5 @@ export interface ISessionDataManager isCameraFollowDisabled: boolean; uiFlags: number; tags: string[]; + getUserDataSnapshot(): Readonly; } diff --git a/packages/api/src/nitro/session/IUserDataSnapshot.ts b/packages/api/src/nitro/session/IUserDataSnapshot.ts new file mode 100644 index 0000000..84c971f --- /dev/null +++ b/packages/api/src/nitro/session/IUserDataSnapshot.ts @@ -0,0 +1,22 @@ +export interface IUserDataSnapshot +{ + userId: number; + userName: string; + figure: string; + gender: string; + realName: string; + respectsReceived: number; + respectsLeft: number; + respectsPetLeft: number; + canChangeName: boolean; + clubLevel: number; + securityLevel: number; + isAmbassador: boolean; + isEmailVerified: boolean; + isNoob: boolean; + isAuthenticHabbo: boolean; + isSystemOpen: boolean; + isSystemShutdown: boolean; + uiFlags: number; + tags: ReadonlyArray; +} diff --git a/packages/api/src/nitro/session/index.ts b/packages/api/src/nitro/session/index.ts index 1fe774d..c93302b 100644 --- a/packages/api/src/nitro/session/index.ts +++ b/packages/api/src/nitro/session/index.ts @@ -18,6 +18,8 @@ export * from './IRoomSessionManager'; export * from './IRoomUserData'; export * from './ISessionDataManager'; export * from './IUserDataManager'; +export * from './IUserDataSnapshot'; +export * from './IRoomSessionSnapshot'; export * from './PetBreedingResultData'; export * from './PetCustomPart'; export * from './PetFigureData'; diff --git a/packages/communication/src/CommunicationManager.ts b/packages/communication/src/CommunicationManager.ts index a9d2cad..c783183 100644 --- a/packages/communication/src/CommunicationManager.ts +++ b/packages/communication/src/CommunicationManager.ts @@ -203,6 +203,17 @@ export class CommunicationManager implements ICommunicationManager this._connection.removeMessageEvent(event); } + public subscribeMessage(eventCtor: new (callback: (event: T) => void) => T, handler: (event: T) => void): () => void + { + if(!eventCtor || !handler) return () => {}; + + const event = new eventCtor(handler); + + this.registerMessageEvent(event); + + return () => this.removeMessageEvent(event); + } + public get connection(): IConnection { return this._connection; diff --git a/packages/events/src/EventDispatcher.ts b/packages/events/src/EventDispatcher.ts index f95aa3c..5c21b00 100644 --- a/packages/events/src/EventDispatcher.ts +++ b/packages/events/src/EventDispatcher.ts @@ -101,4 +101,23 @@ export class EventDispatcher implements IEventDispatcher { this._listeners.clear(); } + + public subscribe(type: string | string[], callback: (event: T) => void): () => void + { + if(!type || !callback) return () => {}; + + if(Array.isArray(type)) + { + for(const t of type) this.addEventListener(t, callback); + + return () => + { + for(const t of type) this.removeEventListener(t, callback); + }; + } + + this.addEventListener(type, callback); + + return () => this.removeEventListener(type, callback); + } } diff --git a/packages/events/src/NitroEventType.ts b/packages/events/src/NitroEventType.ts index 9e575ac..6cfd367 100644 --- a/packages/events/src/NitroEventType.ts +++ b/packages/events/src/NitroEventType.ts @@ -17,4 +17,6 @@ export class NitroEventType public static readonly AVATAR_EFFECT_DOWNLOADED = 'AVATAR_EFFECT_DOWNLOADED'; public static readonly AVATAR_EFFECT_LOADED = 'AVATAR_EFFECT_LOADED'; public static readonly FURNITURE_DATA_LOADED = 'FURNITURE_DATA_LOADED'; + public static readonly SESSION_DATA_UPDATED = 'SESSION_DATA_UPDATED'; + public static readonly ROOM_SESSION_UPDATED = 'ROOM_SESSION_UPDATED'; } diff --git a/packages/session/src/RoomSessionManager.ts b/packages/session/src/RoomSessionManager.ts index 0ffd0eb..52b206f 100644 --- a/packages/session/src/RoomSessionManager.ts +++ b/packages/session/src/RoomSessionManager.ts @@ -1,6 +1,6 @@ -import { IRoomHandlerListener, IRoomSession, IRoomSessionManager } from '@nitrots/api'; +import { IRoomHandlerListener, IRoomSession, IRoomSessionManager, IRoomSessionSnapshot } from '@nitrots/api'; import { GetCommunication, RoomEnterComposer, RoomUnitWalkComposer } from '@nitrots/communication'; -import { GetEventDispatcher, NitroEventType, RoomSessionEvent } from '@nitrots/events'; +import { GetEventDispatcher, NitroEvent, NitroEventType, RoomSessionEvent } from '@nitrots/events'; import { NitroLogger } from '@nitrots/utils'; import { RoomSession } from './RoomSession'; import { BaseHandler, GenericErrorHandler, PetPackageHandler, PollHandler, RoomChatHandler, RoomDataHandler, RoomDimmerPresetsHandler, RoomPermissionsHandler, RoomPresentHandler, RoomSessionHandler, RoomUsersHandler, WordQuizHandler } from './handler'; @@ -26,6 +26,41 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList private _pendingRoomClear: ReturnType = null; private _savedPosX: number = -1; private _savedPosY: number = -1; + private _activeRoomSessionSnapshot: Readonly | null = null; + + private invalidateRoomSessionSnapshot(): void + { + this._activeRoomSessionSnapshot = null; + + GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.ROOM_SESSION_UPDATED)); + } + + public getActiveRoomSessionSnapshot(): Readonly | null + { + const session = this._viewerSession; + + if(!session) return null; + + if(this._activeRoomSessionSnapshot && this._activeRoomSessionSnapshot.session === session) return this._activeRoomSessionSnapshot; + + this._activeRoomSessionSnapshot = Object.freeze({ + roomId: session.roomId, + state: session.state, + isRoomOwner: session.isRoomOwner, + isSpectator: session.isSpectator, + isDecorating: session.isDecorating, + isGuildRoom: session.isGuildRoom, + isPrivateRoom: session.isPrivateRoom, + controllerLevel: session.controllerLevel, + doorMode: session.doorMode, + tradeMode: session.tradeMode, + allowPets: session.allowPets, + groupId: session.groupId, + session + }); + + return this._activeRoomSessionSnapshot; + } public async init(): Promise { @@ -196,6 +231,7 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList this._sessions.clear(); this._viewerSession = null; + this.invalidateRoomSessionSnapshot(); this.createSession(roomId, password, this._savedPosX, this._savedPosY); this.clearGuardTimer(); this._reconnectGuardTimer = setTimeout(() => @@ -384,6 +420,8 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList this._lastRoomPassword = roomSession.password; this.persistRoom(roomSession.roomId, roomSession.password); + this.invalidateRoomSessionSnapshot(); + this.startSession(this._viewerSession); return true; @@ -406,6 +444,8 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList this.setHandlers(session); + this.invalidateRoomSessionSnapshot(); + return true; } @@ -429,6 +469,10 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList } GetEventDispatcher().dispatchEvent(new RoomSessionEvent(RoomSessionEvent.ENDED, session, openLandingView)); + + if(this._viewerSession === session) this._viewerSession = null; + + this.invalidateRoomSessionSnapshot(); } public sessionUpdate(id: number, type: string): void diff --git a/packages/session/src/SessionDataManager.ts b/packages/session/src/SessionDataManager.ts index 9d3c07f..c92c52a 100644 --- a/packages/session/src/SessionDataManager.ts +++ b/packages/session/src/SessionDataManager.ts @@ -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 | null = null; + constructor() { this.resetUserInfo(); } + private invalidateUserDataSnapshot(): void + { + this._userDataSnapshot = null; + + GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SESSION_DATA_UPDATED)); + } + + public getUserDataSnapshot(): Readonly + { + if(this._userDataSnapshot) return this._userDataSnapshot; + + this._userDataSnapshot = Object.freeze({ + 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([...this._tags]) as ReadonlyArray + }); + + return this._userDataSnapshot; + } + public async init(): Promise { 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.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('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 From c7a5aea98a6014d5d0dc3c5a3efd449502ad7bbf Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sun, 10 May 2026 19:27:37 +0200 Subject: [PATCH 02/30] =?UTF-8?q?chore(ts):=20bump=20TypeScript=205.8=20?= =?UTF-8?q?=E2=86=92=206.0=20and=20add=20tsgo=20for=20fast=20type-checking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - typescript: ~5.8.2 → ^6.0.3 (matches Nitro-V3 client) - adds @typescript/native-preview (tsgo) as TypeScript 7 preview - new `compile:fast` script using tsgo (~7× faster: 2.5s vs 17.6s) - tsconfig cleanup ahead of TypeScript 7 deprecations: - removed `baseUrl` (unused: no `paths` mappings on this project) - removed `downlevelIteration` (target ES2022 makes it a no-op) - `moduleResolution`: "Node" → "bundler" (vite consumes the renderer) Compile errors: 28 → 29. Net +1 because TS 6's tightened lib types flag two pre-existing crypto calls (WsSessionCrypto.ts:43,48) and resolves one prior false positive. All errors are in pre-existing code, unrelated to the new event/snapshot APIs from 791b8ad. --- package.json | 4 +++- tsconfig.json | 4 +--- yarn.lock | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index ff24624..bdec0b0 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "scripts": { "build": "vite build", "compile": "tsc --project ./tsconfig.json --noEmit false", + "compile:fast": "tsgo --project ./tsconfig.json --noEmit", "eslint": "eslint ./src ./packages/*/src", "eslint-fix": "eslint ./src --fix", "test": "vitest run", @@ -46,8 +47,9 @@ "@vitest/coverage-v8": "^4.0.18", "eslint": "^9.8.0", "jsdom": "^27.4.0", + "@typescript/native-preview": "^7.0.0-dev.20260510.1", "tslib": "^2.6.3", - "typescript": "~5.8.2", + "typescript": "^6.0.3", "typescript-eslint": "^8.26.1", "vite": "^8.0.10", "vitest": "^4.1.5" diff --git a/tsconfig.json b/tsconfig.json index c6b52f8..0463a3c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,15 @@ { "compileOnSave": false, "compilerOptions": { - "baseUrl": "./src", "outDir": "./dist", "sourceMap": false, "declaration": true, "experimentalDecorators": true, - "moduleResolution": "Node", + "moduleResolution": "bundler", "esModuleInterop": true, "importHelpers": true, "isolatedModules": true, "resolveJsonModule": true, - "downlevelIteration": true, "allowSyntheticDefaultImports": true, "allowJs": true, "skipLibCheck": true, diff --git a/yarn.lock b/yarn.lock index 19c1a58..00446ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -527,6 +527,54 @@ "@typescript-eslint/types" "8.59.2" eslint-visitor-keys "^5.0.0" +"@typescript/native-preview-darwin-arm64@7.0.0-dev.20260510.1": + version "7.0.0-dev.20260510.1" + resolved "https://registry.yarnpkg.com/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20260510.1.tgz#318a841447a879e9590edf549fbf2b231a109d91" + integrity sha512-YpG99bf/Va1aLGP8SUQy1ClUvi4c6uTFrEQ0B5KzZb9TsOwH1RIrc/2n8UO3IAuilvwEA0EU4q8fEO3otVP2Sw== + +"@typescript/native-preview-darwin-x64@7.0.0-dev.20260510.1": + version "7.0.0-dev.20260510.1" + resolved "https://registry.yarnpkg.com/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20260510.1.tgz#6ca57655a39f13acc7f868c3e44aff2edaa4b02b" + integrity sha512-NUwhwHpQn7aSX2GGBuY2bjec+hFnIz2DAna4ksVneexVE20h2U0MFzBvWrqH2C0PzPxVvGOMg4fGCvhTs93nlw== + +"@typescript/native-preview-linux-arm64@7.0.0-dev.20260510.1": + version "7.0.0-dev.20260510.1" + resolved "https://registry.yarnpkg.com/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20260510.1.tgz#2ad2edc4ca931d9697956093d63f32970ce10fed" + integrity sha512-T7Zuy6h0sU+38w+N3A+YgW0XVqxIMjeHyu+945rJkiP9zk52Mwp663t1ndyeAE/N2zV+q0SWQmHNuFSXl99wJw== + +"@typescript/native-preview-linux-arm@7.0.0-dev.20260510.1": + version "7.0.0-dev.20260510.1" + resolved "https://registry.yarnpkg.com/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20260510.1.tgz#e01d73e1bf9fd5780326b35ab7cc97a3f9836d01" + integrity sha512-UE+PIWWg7vvszSU0gS9rzgIIHCWexz3hMZDHpHRSLAleAvULCNI3EzwTRFOA4BHyQ8eReD1KZ8e76BuStEPspw== + +"@typescript/native-preview-linux-x64@7.0.0-dev.20260510.1": + version "7.0.0-dev.20260510.1" + resolved "https://registry.yarnpkg.com/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20260510.1.tgz#9a1ff6947c184fcb548856e1b480adc106de1a5b" + integrity sha512-gJu4q4YREvjR2Lx1jUaCd/bRbTuyKf2r3rJ4tReuHyAvNse23HdGI0a9w4Z3wUbvRznxYt640IIItWsr/f3LEQ== + +"@typescript/native-preview-win32-arm64@7.0.0-dev.20260510.1": + version "7.0.0-dev.20260510.1" + resolved "https://registry.yarnpkg.com/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20260510.1.tgz#d517af09f7ff86b9e7104714f975880c56ad4de9" + integrity sha512-00DtjrtkdAHOU/soYr8ncrjUvIsple8nvb29ZUATnLraNnzUgv5AS3yMve/pG/N7rVLlKy2FrXlVyVW7WAx29w== + +"@typescript/native-preview-win32-x64@7.0.0-dev.20260510.1": + version "7.0.0-dev.20260510.1" + resolved "https://registry.yarnpkg.com/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20260510.1.tgz#1896d9112a99dbf24cc90814d8b6e0fd76b98d6b" + integrity sha512-27UeujQTEPFxhfkZL7aHnA1TlNol3nwDVFp5d6jFoP14yTXMe47kBnAJLEU2ta3REZE5PzLCs7HLV8H4VdxGgA== + +"@typescript/native-preview@^7.0.0-dev.20260510.1": + version "7.0.0-dev.20260510.1" + resolved "https://registry.yarnpkg.com/@typescript/native-preview/-/native-preview-7.0.0-dev.20260510.1.tgz#983a6d94d132b34d916b95bb1e3d0e687525d12a" + integrity sha512-05U6/Im+vmqGrFAVrHSeuoXBCwShhbiA+93VpSwEBYP4LMWk2JW9q87MydamL5g6ISEjIVlwQ4Dx35CauPAwpA== + optionalDependencies: + "@typescript/native-preview-darwin-arm64" "7.0.0-dev.20260510.1" + "@typescript/native-preview-darwin-x64" "7.0.0-dev.20260510.1" + "@typescript/native-preview-linux-arm" "7.0.0-dev.20260510.1" + "@typescript/native-preview-linux-arm64" "7.0.0-dev.20260510.1" + "@typescript/native-preview-linux-x64" "7.0.0-dev.20260510.1" + "@typescript/native-preview-win32-arm64" "7.0.0-dev.20260510.1" + "@typescript/native-preview-win32-x64" "7.0.0-dev.20260510.1" + "@vitest/coverage-v8@^4.0.18": version "4.1.5" resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-4.1.5.tgz#26bbdbebecd66be77fa1b63a9ed985dd86a3ba85" @@ -1676,6 +1724,11 @@ typescript-eslint@^8.26.1: "@typescript-eslint/typescript-estree" "8.59.2" "@typescript-eslint/utils" "8.59.2" +typescript@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.3.tgz#90251dc007916e972786cb94d74d15b185577d21" + integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== + typescript@~5.5.4: version "5.5.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" From ddb7222b6670ddf4c8e69744f1e1b0e26b16c5ee Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sun, 10 May 2026 21:29:50 +0200 Subject: [PATCH 03/30] chore: bump TypeScript pins to ^6.0.3 across all 12 workspaces + thumbmarkjs 1.9 + add CLAUDE.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each workspace package was still pinning `typescript: ~5.5.x` or `~5.8.2` in its own devDependencies even though the root bumped to 6.0.3 in 60b1143. The pins were dead (yarn 1 hoists from root) but they're misleading when reading a single package.json. Bring them all to `^6.0.3` to match the root. Other: - @thumbmarkjs/thumbmarkjs 1.8.1 → 1.9.0 (root + communication package) - yarn.lock regenerated from a clean install (vitest 4 hoisting was flaking on the patch vite bump; reverted vite to ^8.0.10) Adds CLAUDE.md at the repo root: short project context for future sessions — stack, the 12-workspace layout, the React-friendly v2.1.0 additions (`subscribe()`, `subscribeMessage()`, snapshot getters), build scripts, and known gotchas (`SessionDataManager.getUserData` does NOT exist; sendChat* expects 3 args; dispatchEvent is sync). --- CLAUDE.md | 128 +++++++++++++++ package.json | 2 +- packages/api/package.json | 2 +- packages/assets/package.json | 6 +- packages/avatar/package.json | 2 +- packages/camera/package.json | 2 +- packages/communication/package.json | 4 +- packages/configuration/package.json | 2 +- packages/events/package.json | 2 +- packages/localization/package.json | 2 +- packages/room/package.json | 2 +- packages/session/package.json | 2 +- packages/sound/package.json | 2 +- packages/utils/package.json | 2 +- yarn.lock | 232 +++++++++++++--------------- 15 files changed, 255 insertions(+), 137 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9370c09 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,128 @@ +# Nitro_Render_V3 — Claude project context + +Pure-TypeScript renderer library for the Nitro retro Habbo client. +Wraps **PixiJS v8** for room/avatar rendering and provides the WebSocket ++ event-bus infrastructure that the React client (`../Nitro-V3`) sits on +top of. + +## Stack + +- **TypeScript 6.0** (root) + **tsgo** (`@typescript/native-preview`, + TS 7 preview compiler — used by `yarn compile:fast`, ~7× faster on + this codebase) +- **PixiJS v8** (`pixi.js@8.18`) +- **Vite 8** for build + bundling +- **Vitest 4** for unit tests +- **Yarn 1.22 workspaces** (`packages/*`) — note: yarn 1, NOT yarn 4 like + the client. The two repos use different package managers on purpose. +- **No React** — this is a pure TS library; React lives in `../Nitro-V3`. + +## Workspace layout + +Twelve internal packages under `packages/*/src/`, each pinning +`typescript: ^6.0.3` in its own `devDependencies`: + +``` +packages/ + api public interfaces (IEventDispatcher, ISessionDataManager, ...) + assets asset loading + caching + avatar avatar rendering / figure resolution + camera in-room camera widget + communication WebSocket + composer/parser pipeline + configuration runtime config loader + events EventDispatcher + NitroEventType + per-domain events + localization LocalizationManager + room RoomEngine + RoomVisualization + session SessionDataManager + RoomSessionManager + handlers + sound SoundManager (howler-based) + utils shared utilities (BinaryReader, Logger, …) +``` + +Root `index.ts` re-exports everything from `@nitrots/*` so the React +client gets a flat `import { … } from '@nitrots/nitro-renderer'`. + +## React-friendly API additions (v2.1.0) + +Three additions matter for the React client integration. Keep these +backwards-compatible: + +### `EventDispatcher.subscribe(type, callback): () => void` + +Signature matches what `useSyncExternalStore` expects — returns an +unsubscriber, no need to juggle callback identity. Implemented in +`packages/events/src/EventDispatcher.ts`. The legacy +`addEventListener` / `removeEventListener` still work. + +### `CommunicationManager.subscribeMessage(eventCtor, handler): () => void` + +Equivalent for packet streams. Implemented in +`packages/communication/src/CommunicationManager.ts`. + +### Snapshot getters on `SessionDataManager` + `RoomSessionManager` + +```ts +getUserDataSnapshot(): Readonly +getActiveRoomSessionSnapshot(): Readonly | null +``` + +Returns **referentially-stable** values: the same object reference is +returned across reads until invalidated. Invalidation happens via the +new event types `NitroEventType.SESSION_DATA_UPDATED` and +`NitroEventType.ROOM_SESSION_UPDATED`. + +When you mutate any field that the snapshot exposes, call the private +`invalidateUserDataSnapshot()` / `invalidateRoomSessionSnapshot()` — +that drops the cached snapshot and dispatches the invalidation event. +The React side rebuilds via `useSyncExternalStore`. + +The interface contracts live in: +- `packages/api/src/nitro/session/IUserDataSnapshot.ts` +- `packages/api/src/nitro/session/IRoomSessionSnapshot.ts` + +## Scripts + +``` +yarn build # vite build +yarn compile # tsc --project ./tsconfig.json --noEmit false +yarn compile:fast # tsgo (~7× faster, TS 7 preview) +yarn eslint # lint src + packages/*/src +yarn test # vitest run +yarn test:watch # vitest watch +yarn test:coverage # vitest with v8 coverage +``` + +## Consumed by + +`../Nitro-V3` consumes this library via `link:../Nitro_Render_V3` +(yarn 4 node-modules linker). DO NOT use `yarn link` — it confuses +vite's asset resolution. The client's `vite.config.js` then maps each +`@nitrots/*` package directly to its source `index.ts` so there's no +build step needed for development. + +When making changes to renderer APIs the React client uses, the +client's `feat/react19-*` branches contain consumers — check +`Nitro-V3/src/hooks/events/` and `Nitro-V3/src/hooks/{session,rooms}/` +for the React-side bridge code. + +## Gotchas + +- **`SessionDataManager.getUserData(id)` does NOT exist.** Some legacy + code in the React client used it under a `getUserData ?` truthy guard; + the branch was always dead. Only `getUserDataSnapshot()` exists. +- `IRoomSession.sendChatMessage` / `sendShoutMessage` require **3 args** + (text, styleId, chatColour). The React client's chat-input legacy + passes 2 — known pre-existing gap, do not "fix" the client without + also threading chatColour through the chat composer pipeline. +- The renderer is **synchronous**: `EventDispatcher.dispatchEvent` is a + synchronous loop over listeners. Don't add `await` inside the + `processEvent` loop — it would change ordering guarantees that + consumers rely on. +- Workspace package devDeps pin TS at `^6.0.3` so `yarn compile` inside + any single package keeps working. The root TS 6 is the source of + truth. + +## Sister projects in the same DEV folder + +- `../Nitro-V3` — React 19 client (consumes this lib via link) +- `../Arcturus-Morningstar-Extended` — Java emulator (server side) +- `../NitroV3-Housekeeping` — Next.js + Prisma admin CMS diff --git a/package.json b/package.json index bdec0b0..e007a89 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ }, "main": "./index", "dependencies": { - "@thumbmarkjs/thumbmarkjs": "^1.8.1", + "@thumbmarkjs/thumbmarkjs": "^1.9.0", "gifuct-js": "^2.1.2", "howler": "^2.2.4", "pako": "^2.1.0", diff --git a/packages/api/package.json b/packages/api/package.json index ac5ffdb..db9f80d 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -15,6 +15,6 @@ "pixi.js": "^8.8.1" }, "devDependencies": { - "typescript": "~5.8.2" + "typescript": "^6.0.3" } } diff --git a/packages/assets/package.json b/packages/assets/package.json index 00a013b..3fe5a7a 100644 --- a/packages/assets/package.json +++ b/packages/assets/package.json @@ -14,10 +14,10 @@ "dependencies": { "@nitrots/api": "1.0.0", "@nitrots/utils": "1.0.0", - "@pixi/gif": "^3.0.1", - "pixi.js": "^8.8.1" + "@pixi/gif": "^3.0.1", + "pixi.js": "^8.8.1" }, "devDependencies": { - "typescript": "~5.8.2" + "typescript": "^6.0.3" } } diff --git a/packages/avatar/package.json b/packages/avatar/package.json index 3f70573..ee629be 100644 --- a/packages/avatar/package.json +++ b/packages/avatar/package.json @@ -15,6 +15,6 @@ "@nitrots/utils": "1.0.0" }, "devDependencies": { - "typescript": "~5.8.2" + "typescript": "^6.0.3" } } diff --git a/packages/camera/package.json b/packages/camera/package.json index d189993..cb6cc73 100644 --- a/packages/camera/package.json +++ b/packages/camera/package.json @@ -17,6 +17,6 @@ "pixi.js": "^8.8.1" }, "devDependencies": { - "typescript": "~5.8.2" + "typescript": "^6.0.3" } } diff --git a/packages/communication/package.json b/packages/communication/package.json index 7d05ed6..fc402e8 100644 --- a/packages/communication/package.json +++ b/packages/communication/package.json @@ -12,9 +12,9 @@ "@nitrots/api": "1.0.0", "@nitrots/events": "1.0.0", "@nitrots/utils": "1.0.0", - "@thumbmarkjs/thumbmarkjs": "^1.8.1" + "@thumbmarkjs/thumbmarkjs": "^1.9.0" }, "devDependencies": { - "typescript": "~5.8.2" + "typescript": "^6.0.3" } } diff --git a/packages/configuration/package.json b/packages/configuration/package.json index 337c839..24d9f30 100644 --- a/packages/configuration/package.json +++ b/packages/configuration/package.json @@ -13,6 +13,6 @@ "@nitrots/utils": "1.0.0" }, "devDependencies": { - "typescript": "~5.8.2" + "typescript": "^6.0.3" } } diff --git a/packages/events/package.json b/packages/events/package.json index 85c4b86..3b445e6 100644 --- a/packages/events/package.json +++ b/packages/events/package.json @@ -13,6 +13,6 @@ "@nitrots/utils": "1.0.0" }, "devDependencies": { - "typescript": "~5.8.2" + "typescript": "^6.0.3" } } diff --git a/packages/localization/package.json b/packages/localization/package.json index 3dcfc43..c275ef3 100644 --- a/packages/localization/package.json +++ b/packages/localization/package.json @@ -16,6 +16,6 @@ "pixi.js": "^8.8.1" }, "devDependencies": { - "typescript": "~5.8.2" + "typescript": "^6.0.3" } } diff --git a/packages/room/package.json b/packages/room/package.json index 71e36d4..095815a 100644 --- a/packages/room/package.json +++ b/packages/room/package.json @@ -19,6 +19,6 @@ "pixi.js": "^8.8.1" }, "devDependencies": { - "typescript": "~5.8.2" + "typescript": "^6.0.3" } } diff --git a/packages/session/package.json b/packages/session/package.json index 699adc4..d6edb8e 100644 --- a/packages/session/package.json +++ b/packages/session/package.json @@ -18,6 +18,6 @@ "pixi.js": "^8.8.1" }, "devDependencies": { - "typescript": "~5.8.2" + "typescript": "^6.0.3" } } diff --git a/packages/sound/package.json b/packages/sound/package.json index 53bbf1b..f3b0661 100644 --- a/packages/sound/package.json +++ b/packages/sound/package.json @@ -14,6 +14,6 @@ "pixi.js": "^8.8.1" }, "devDependencies": { - "typescript": "~5.5.4" + "typescript": "^6.0.3" } } diff --git a/packages/utils/package.json b/packages/utils/package.json index 4c99424..ebd3ad0 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -15,6 +15,6 @@ }, "devDependencies": { "@types/pako": "^2.0.3", - "typescript": "~5.5.4" + "typescript": "^6.0.3" } } diff --git a/yarn.lock b/yarn.lock index 00446ba..257b9cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -248,10 +248,10 @@ dependencies: "@tybys/wasm-util" "^0.10.1" -"@oxc-project/types@=0.127.0": - version "0.127.0" - resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.127.0.tgz#8374fcdfb4a641861218daa5700c447c00b66663" - integrity sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ== +"@oxc-project/types@=0.128.0": + version "0.128.0" + resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.128.0.tgz#efc7524f948ff9e8ab1404ecad1823849c6fe149" + integrity sha512-huv1Y/LzBJkBVHt3OlC7u0zHBW9qXf1FdD7sGmc1rXc2P1mTwHssYv7jyGx5KAACSCH+9B3Bhn6Z9luHRvf7pQ== "@pixi/colord@^2.9.6": version "2.9.6" @@ -263,89 +263,89 @@ resolved "https://registry.yarnpkg.com/@pixi/gif/-/gif-3.0.1.tgz#2709d6559d316161cde1821b0f29cc2c05f88794" integrity sha512-oGl0nkbFAe1vaRLyIvGbJc3fcIrS8vF1E00cwjiV+9f1pYe072D+yijJxHsgYnXs6jdzERh+D0MqSrEag0jRzg== -"@rolldown/binding-android-arm64@1.0.0-rc.17": - version "1.0.0-rc.17" - resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz#0a502a88c39d0ffa81aa30b561dade6f6217dcc5" - integrity sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ== +"@rolldown/binding-android-arm64@1.0.0-rc.18": + version "1.0.0-rc.18" + resolved "https://registry.yarnpkg.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.18.tgz#3af8b2242086125934a85c1915b76e0a6a2054c1" + integrity sha512-lIDyUAfD7U3+BWKzdxMbJcsYHuqXqmGz40aeRqvuAm3y5TkJSYTBW2RDrn65DJFPQqVjUAUqq5uz8urzQ8aBdQ== -"@rolldown/binding-darwin-arm64@1.0.0-rc.17": - version "1.0.0-rc.17" - resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz#8b7f05ac9000ab19161a79a0346b1b64a1bc7ba3" - integrity sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw== +"@rolldown/binding-darwin-arm64@1.0.0-rc.18": + version "1.0.0-rc.18" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.18.tgz#ae0b4467d24ecd6c6589f03d4d4699616ee9649c" + integrity sha512-apJq2ktnGp27nSInMR5Vcj8kY6xJzDAvfdIFlpDcAK/w4cDO58qVoi1YQsES/SKiFNge/6e4CUzgjfHduYqWpQ== -"@rolldown/binding-darwin-x64@1.0.0-rc.17": - version "1.0.0-rc.17" - resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz#f8b465b3a4e992053890b162f1ae19e4f1719a6a" - integrity sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw== +"@rolldown/binding-darwin-x64@1.0.0-rc.18": + version "1.0.0-rc.18" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.18.tgz#23cf24b0a7b96c8990bbdd8a91e7fd3ba82b00e7" + integrity sha512-5Ofot8xbs+pxRHJqm9/9N/4sTQOvdrwEsmPE9pdLEEoAbdZtG6F2LMDfO1sp6ZAtXJuJV/21ew2srq3W8NXB5g== -"@rolldown/binding-freebsd-x64@1.0.0-rc.17": - version "1.0.0-rc.17" - resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz#a8281e14fa9c243fe22dc2d0e54900e66b31935e" - integrity sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw== +"@rolldown/binding-freebsd-x64@1.0.0-rc.18": + version "1.0.0-rc.18" + resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.18.tgz#a047a770f94dc451c062b729e5d1cf82e5c6f9c4" + integrity sha512-7h8eeOTT1eyqJyx64BFCnWZpNm486hGWt2sqeLLgDxA0xI1oGZ9H7gK1S85uNGmBhkdPwa/6reTxfFFKvIsebw== -"@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17": - version "1.0.0-rc.17" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz#cd29cf869ddd4fac8d6929abf94b91ddb0494650" - integrity sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ== +"@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.18": + version "1.0.0-rc.18" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.18.tgz#c0b7f346cbf50301cea669a4632bc63aabe6a72c" + integrity sha512-eRcm/HVt9U/JFu5RKAEKwGQYtDCKWLiaH6wOnsSEp6NMBb/3Os8LgHZlNyzMpFVNmiiMFlfb2zEnebfzJrHFmg== -"@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17": - version "1.0.0-rc.17" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz#91c331236ec3728366218d61a62f0bd226546c6c" - integrity sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q== +"@rolldown/binding-linux-arm64-gnu@1.0.0-rc.18": + version "1.0.0-rc.18" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.18.tgz#af56373c7996ebe6379207cd699c9f7f705e235d" + integrity sha512-SOrT/cT4ukTmgnrEz/Hg3m7LBnuCLW9psDeMKrimRWY4I8DmnO7Lco8W2vtqPmMkbVu8iJ+g4GFLVLLOVjJ9DQ== -"@rolldown/binding-linux-arm64-musl@1.0.0-rc.17": - version "1.0.0-rc.17" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz#80108957db752e7826836e22240e56b8140e9684" - integrity sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg== +"@rolldown/binding-linux-arm64-musl@1.0.0-rc.18": + version "1.0.0-rc.18" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.18.tgz#a8f5acd21fcffc8991aa84710e3ae603c4240ea4" + integrity sha512-QWjdxN1HJCpBTAcZ5N5F7wju3gVPzRzSpmGzx7na0c/1qpN9CFil+xt+l9lV/1M6/gqHSNXCiqPfwhVJPeLnug== -"@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17": - version "1.0.0-rc.17" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz#1dce51148cbc6bab3c3f9157b5323d2a31aac924" - integrity sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA== +"@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.18": + version "1.0.0-rc.18" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.18.tgz#1d4a89e040ff82141fc46e717cfab80b05f7c13f" + integrity sha512-ugCOyj7a4d9h3q9B+wXmf6g3a68UsjGh6dob5DHevHGMwDUbhsYNbSPxJsENcIttJZ9jv7qGM2UesLw5jqIhdg== -"@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17": - version "1.0.0-rc.17" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz#d4a0d2e01d8d441e4ac3af3fa68eec17a7d0e9cd" - integrity sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA== +"@rolldown/binding-linux-s390x-gnu@1.0.0-rc.18": + version "1.0.0-rc.18" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.18.tgz#97c21feeb2ed87d07820f0b2dcc5dd663e7a7f3b" + integrity sha512-kKWRhbsotpXkGbcd5dllUWg5gEXcDAa8u5YnP9AV5DYNbvJHGzzuwv7dpmhc8NqKMJldl0a+x76IHbspEpEmdA== -"@rolldown/binding-linux-x64-gnu@1.0.0-rc.17": - version "1.0.0-rc.17" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz#0ac8b3139cefeea798ad147f30ea70572b133af1" - integrity sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA== +"@rolldown/binding-linux-x64-gnu@1.0.0-rc.18": + version "1.0.0-rc.18" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.18.tgz#06310d40fe139ccc3c433b361120d337c66ebec2" + integrity sha512-uCo8ElcCIAMyYAZyuIZ81oFkhTSIllNvUCHCAlbhlN4ji3uC28h7IIdlXyIvGO7HsuqnV9p3rD/bpH7XhIyhRw== -"@rolldown/binding-linux-x64-musl@1.0.0-rc.17": - version "1.0.0-rc.17" - resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz#2af61bee087571728f58f1c47734bbbd41dd7050" - integrity sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw== +"@rolldown/binding-linux-x64-musl@1.0.0-rc.18": + version "1.0.0-rc.18" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.18.tgz#6a711258841f42609b238050cfcd5db13ac136d0" + integrity sha512-XNOQZtuE6yUIvx4rwGemwh8kpL1xvU41FXy/s9K7T/3JVcqGzo3NfKM2HrbrGgfPYGFW42f07Wk++aOC6B9NWA== -"@rolldown/binding-openharmony-arm64@1.0.0-rc.17": - version "1.0.0-rc.17" - resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz#56c1afbf6c592819abf47b4a983987dc288b30c1" - integrity sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA== +"@rolldown/binding-openharmony-arm64@1.0.0-rc.18": + version "1.0.0-rc.18" + resolved "https://registry.yarnpkg.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.18.tgz#15cb644beeafdbec930d79ed45c2a7c2573eac70" + integrity sha512-tSn/kzrfa7tNOXr7sEacDBN4YsIqTyLqh45IO0nHDwtpKIDNDJr+VFojt+4klSpChxB29JLyduSsE0MKEwa65A== -"@rolldown/binding-wasm32-wasi@1.0.0-rc.17": - version "1.0.0-rc.17" - resolved "https://registry.yarnpkg.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz#5d112ff4dd0d268a60fb4e0eb3077e3ea2531f0d" - integrity sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA== +"@rolldown/binding-wasm32-wasi@1.0.0-rc.18": + version "1.0.0-rc.18" + resolved "https://registry.yarnpkg.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.18.tgz#ca3a56d11dfd533d743711141b3bb4c1ec10110e" + integrity sha512-+J9YGmc+czgqlhYmwun3S3O0FIZhsH8ep2456xwjAdIOmuJxM7xz4P4PtrxU+Bz17a/5bqPA8o3HAAoX0teUdg== dependencies: "@emnapi/core" "1.10.0" "@emnapi/runtime" "1.10.0" "@napi-rs/wasm-runtime" "^1.1.4" -"@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17": - version "1.0.0-rc.17" - resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz#5125a85222d64a543201d28e16a395cc45bf4d17" - integrity sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA== +"@rolldown/binding-win32-arm64-msvc@1.0.0-rc.18": + version "1.0.0-rc.18" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.18.tgz#8c2117d68331d7de59d24631146d538fc203d27c" + integrity sha512-zsu47DgU0FQzSwi6sU9dZoEdUv7pc1AptSEz/Z8HBg54sV0Pbs3N0+CrIbTsgiu6EyoaNN9CHboqbLaz9lhOyQ== -"@rolldown/binding-win32-x64-msvc@1.0.0-rc.17": - version "1.0.0-rc.17" - resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz#fc6b78e759a0bb2054b5c0a3489da12b2cae54b4" - integrity sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg== +"@rolldown/binding-win32-x64-msvc@1.0.0-rc.18": + version "1.0.0-rc.18" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.18.tgz#bb5c28df3095046778cc1b020ef52fc5ee7b7e70" + integrity sha512-7H+3yqGgmnlDTRRhw/xpYY9J1kf4GC681nVc4GqKhExZTDrVVrV2tsOR9kso0fvgBdcTCcQShx4SLLoHgaLwhg== -"@rolldown/pluginutils@1.0.0-rc.17": - version "1.0.0-rc.17" - resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz#a89b30833fb628bc834fe2e89fea93a2da9fa69a" - integrity sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg== +"@rolldown/pluginutils@1.0.0-rc.18": + version "1.0.0-rc.18" + resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.18.tgz#51cf2589596a179ebe8cbf313f1358c7b51a2fdc" + integrity sha512-CUY5Mnhe64xQBGZEEXQ5WyZwsc1JU3vAZLIxtrsBt3LO6UOb+C8GunVKqe9sT8NeWb4lqSaoJtp2xo6GxT1MNw== "@rollup/plugin-typescript@^11.1.6": version "11.1.6" @@ -369,10 +369,10 @@ resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== -"@thumbmarkjs/thumbmarkjs@^1.8.1": - version "1.8.1" - resolved "https://registry.yarnpkg.com/@thumbmarkjs/thumbmarkjs/-/thumbmarkjs-1.8.1.tgz#93f9725e9bc0ba2d6bffdfda27fb23c11da421bb" - integrity sha512-9cX/mC9gC0a4Kr2ZSPO9MR9azangLiD0ANQmvQ/YCqBuinT56phyYg5u97+f5IYx9/4BH9qOXj0oyAnoxrJorA== +"@thumbmarkjs/thumbmarkjs@^1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@thumbmarkjs/thumbmarkjs/-/thumbmarkjs-1.9.0.tgz#a6444ac1f924f061cfc1507a21dcaf83ee705cab" + integrity sha512-6LooyYk8i5L2zEZgDMLE6m2sGDcIHHBiZfxdFp0A16Q4ZXafEmhHmt+zCqQEBMiQHi+08e/v5q77IY2KhvAJwg== "@tybys/wasm-util@^0.10.1": version "0.10.2" @@ -400,9 +400,9 @@ integrity sha512-k/9fOUGO39yd2sCjrbAJvGDEQvRwRnQIZlBz43roGwUZo5SHAmyVvSFyaVVZkicRVCaDXPKlbxrUcBuJoSWunQ== "@types/estree@^1.0.0", "@types/estree@^1.0.6": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" - integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== + version "1.0.9" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24" + integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg== "@types/gradient-parser@^0.1.2": version "0.1.5" @@ -420,9 +420,9 @@ integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== "@types/node@^20.14.12": - version "20.19.39" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.39.tgz#e98a3b575574070cd34b784bd173767269f95e99" - integrity sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw== + version "20.19.40" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.40.tgz#80a4a7236e27817636777836ceedb889adf6da2f" + integrity sha512-xxx6M2IpSTnnKcR0cMvIiohkiCx20/oRPtWGbenFygKCGl3zqUzdNjQ/1V4solq1LU+dgv0nQzeGOuqkqZGg0Q== dependencies: undici-types "~6.21.0" @@ -738,9 +738,9 @@ brace-expansion@^1.1.7: concat-map "0.0.1" brace-expansion@^5.0.5: - version "5.0.5" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.5.tgz#dcc3a37116b79f3e1b46db994ced5d570e930fdb" - integrity sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ== + version "5.0.6" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.6.tgz#ec68fe0a641a29d8711579caf641d05bae1f2285" + integrity sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g== dependencies: balanced-match "^4.0.2" @@ -1514,7 +1514,7 @@ pixi.js@^8.18.1, pixi.js@^8.8.1: parse-svg-path "^0.1.2" tiny-lru "^11.4.7" -postcss@^8.5.10: +postcss@^8.5.14: version "8.5.14" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.14.tgz#a66c2d7808fadf69ebb5b84a03f8bafd76c4919c" integrity sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg== @@ -1553,29 +1553,29 @@ resolve@^1.22.1: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -rolldown@1.0.0-rc.17: - version "1.0.0-rc.17" - resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.0.0-rc.17.tgz#c524fc22f6bb37b5588aec862ab1ee11382610f3" - integrity sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA== +rolldown@1.0.0-rc.18: + version "1.0.0-rc.18" + resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.0.0-rc.18.tgz#c597f89a4ce12e6fc918fa91e4f892b340aa92f0" + integrity sha512-phmyKBpuBdRYDf4hgyynGAYn/rDDe+iZXKVJ7WX5b1zQzpLkP5oJRPGsfJuHdzPMlyyEO/4sPW6yfSx2gf7lVg== dependencies: - "@oxc-project/types" "=0.127.0" - "@rolldown/pluginutils" "1.0.0-rc.17" + "@oxc-project/types" "=0.128.0" + "@rolldown/pluginutils" "1.0.0-rc.18" optionalDependencies: - "@rolldown/binding-android-arm64" "1.0.0-rc.17" - "@rolldown/binding-darwin-arm64" "1.0.0-rc.17" - "@rolldown/binding-darwin-x64" "1.0.0-rc.17" - "@rolldown/binding-freebsd-x64" "1.0.0-rc.17" - "@rolldown/binding-linux-arm-gnueabihf" "1.0.0-rc.17" - "@rolldown/binding-linux-arm64-gnu" "1.0.0-rc.17" - "@rolldown/binding-linux-arm64-musl" "1.0.0-rc.17" - "@rolldown/binding-linux-ppc64-gnu" "1.0.0-rc.17" - "@rolldown/binding-linux-s390x-gnu" "1.0.0-rc.17" - "@rolldown/binding-linux-x64-gnu" "1.0.0-rc.17" - "@rolldown/binding-linux-x64-musl" "1.0.0-rc.17" - "@rolldown/binding-openharmony-arm64" "1.0.0-rc.17" - "@rolldown/binding-wasm32-wasi" "1.0.0-rc.17" - "@rolldown/binding-win32-arm64-msvc" "1.0.0-rc.17" - "@rolldown/binding-win32-x64-msvc" "1.0.0-rc.17" + "@rolldown/binding-android-arm64" "1.0.0-rc.18" + "@rolldown/binding-darwin-arm64" "1.0.0-rc.18" + "@rolldown/binding-darwin-x64" "1.0.0-rc.18" + "@rolldown/binding-freebsd-x64" "1.0.0-rc.18" + "@rolldown/binding-linux-arm-gnueabihf" "1.0.0-rc.18" + "@rolldown/binding-linux-arm64-gnu" "1.0.0-rc.18" + "@rolldown/binding-linux-arm64-musl" "1.0.0-rc.18" + "@rolldown/binding-linux-ppc64-gnu" "1.0.0-rc.18" + "@rolldown/binding-linux-s390x-gnu" "1.0.0-rc.18" + "@rolldown/binding-linux-x64-gnu" "1.0.0-rc.18" + "@rolldown/binding-linux-x64-musl" "1.0.0-rc.18" + "@rolldown/binding-openharmony-arm64" "1.0.0-rc.18" + "@rolldown/binding-wasm32-wasi" "1.0.0-rc.18" + "@rolldown/binding-win32-arm64-msvc" "1.0.0-rc.18" + "@rolldown/binding-win32-x64-msvc" "1.0.0-rc.18" saxes@^6.0.0: version "6.0.0" @@ -1585,9 +1585,9 @@ saxes@^6.0.0: xmlchars "^2.2.0" semver@^7.5.3, semver@^7.7.3: - version "7.7.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" - integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== + version "7.8.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.0.tgz#ed0661039fcbcda2ce71f01fa6adbefaa77040df" + integrity sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA== shebang-command@^2.0.0: version "2.0.0" @@ -1729,16 +1729,6 @@ typescript@^6.0.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.3.tgz#90251dc007916e972786cb94d74d15b185577d21" integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== -typescript@~5.5.4: - version "5.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" - integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== - -typescript@~5.8.2: - version "5.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" - integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== - undici-types@~6.21.0: version "6.21.0" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" @@ -1752,14 +1742,14 @@ uri-js@^4.2.2: punycode "^2.1.0" "vite@^6.0.0 || ^7.0.0 || ^8.0.0", vite@^8.0.10: - version "8.0.10" - resolved "https://registry.yarnpkg.com/vite/-/vite-8.0.10.tgz#fb31868526ec874101fac084172a2cdc6776319b" - integrity sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw== + version "8.0.11" + resolved "https://registry.yarnpkg.com/vite/-/vite-8.0.11.tgz#d128fe82a0dd24da5127d20560735f1cd7ade0a6" + integrity sha512-Jz1mxtUBR5xTT65VOdJZUUeoyLtqljmFkiUXhPTLZka3RDc9vpi/xXkyrnsdRcm2lIi3l3GPMnAidTsEGIj3Ow== dependencies: lightningcss "^1.32.0" picomatch "^4.0.4" - postcss "^8.5.10" - rolldown "1.0.0-rc.17" + postcss "^8.5.14" + rolldown "1.0.0-rc.18" tinyglobby "^0.2.16" optionalDependencies: fsevents "~2.3.3" From e82d3e03be7f5387253ec03b05f63a35a88ce57f Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sun, 10 May 2026 21:46:10 +0200 Subject: [PATCH 04/30] chore(types): augment ImportMeta with glob signature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `AssetManager.loadRoomImages()` and friends use `import.meta.glob('./assets/...', { eager: true })` to bundle PNG assets via Vite, but TypeScript doesn't see `glob` on ImportMeta without pulling `vite/client` — which we avoid here so the React client (which has its own asset declarations) keeps full control. src/globals.d.ts adds just the `glob` signature, typed for the eager image case (`Record`). The call sites' existing `mod.default ?? mod` narrowing still works. Net renderer typecheck: 29 → 26 (-3 errors). --- src/globals.d.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/globals.d.ts diff --git a/src/globals.d.ts b/src/globals.d.ts new file mode 100644 index 0000000..3c288ac --- /dev/null +++ b/src/globals.d.ts @@ -0,0 +1,17 @@ +/** + * Vite injects `import.meta.glob(pattern, options)` at runtime but TS + * doesn't see it without `vite/client` types — and we don't want to pull + * the full `vite/client` because it overrides asset module declarations + * the consumer (`../Nitro-V3`) owns. Augment `ImportMeta` with just the + * glob signature. + * + * For eager image globs (the only flavor `AssetManager` uses) Vite + * returns `{ default: }`; the call sites then narrow with + * `mod.default ?? mod` for back-compat. The return type below covers + * the eager case directly. Default generic is typed loosely to allow + * `(mod.default ?? mod) as string` patterns. + */ +interface ImportMeta +{ + glob: (pattern: string, options?: { eager?: boolean; import?: string }) => Record; +} From afb5f33ec213921526fc532ed78f958904b4a306 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sun, 10 May 2026 21:48:49 +0200 Subject: [PATCH 05/30] fix(api): IRoomSession.password + sendBackgroundMessage + optional chatColour MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IRoomSession interface was missing three things that have always existed on the RoomSession implementation: - `password: string` — the room session's join password (used by the reconnect flow in RoomSessionManager). - `sendBackgroundMessage(backgroundImage, backgroundStand, backgroundOverlay, backgroundCard?)` — sends the profile-background composer (used by the React client's BackgroundsView). Plus a signature relaxation: - `sendChatMessage` / `sendShoutMessage` `chatColour` is now optional. The implementation already accepted `undefined` (the composer forwards it through), and every historical call site in the React client passes only 2 args — making the 3rd optional simply types reality. Net renderer typecheck: 26 → 23. The change also drops 7 errors on the consumer side (see ../Nitro-V3 typecheck after the workspace link picks this up). CLAUDE.md gotchas updated to reflect the new interface contract. --- CLAUDE.md | 12 ++++++++---- packages/api/src/nitro/session/IRoomSession.ts | 6 ++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 9370c09..ed82000 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -109,10 +109,14 @@ for the React-side bridge code. - **`SessionDataManager.getUserData(id)` does NOT exist.** Some legacy code in the React client used it under a `getUserData ?` truthy guard; the branch was always dead. Only `getUserDataSnapshot()` exists. -- `IRoomSession.sendChatMessage` / `sendShoutMessage` require **3 args** - (text, styleId, chatColour). The React client's chat-input legacy - passes 2 — known pre-existing gap, do not "fix" the client without - also threading chatColour through the chat composer pipeline. +- `IRoomSession.sendChatMessage` / `sendShoutMessage` accept an optional + `chatColour` 3rd arg (was required pre-2.1.1, now optional to match + the historical call sites in the React client). The implementation + forwards `undefined` to the composer just fine; pass a value only when + you need a specific bubble colour. +- `IRoomSession.password` and `IRoomSession.sendBackgroundMessage` are + now part of the public interface (they always existed on the + implementation class — interface caught up). - The renderer is **synchronous**: `EventDispatcher.dispatchEvent` is a synchronous loop over listeners. Don't add `await` inside the `processEvent` loop — it would change ordering guarantees that diff --git a/packages/api/src/nitro/session/IRoomSession.ts b/packages/api/src/nitro/session/IRoomSession.ts index 02a905e..d12ad5a 100644 --- a/packages/api/src/nitro/session/IRoomSession.ts +++ b/packages/api/src/nitro/session/IRoomSession.ts @@ -9,10 +9,11 @@ export interface IRoomSession setRoomOwner(): void; start(): boolean; reset(roomId: number): void; - sendChatMessage(text: string, styleId: number, chatColour: string): void; - sendShoutMessage(text: string, styleId: number, chatColour: string): void; + sendChatMessage(text: string, styleId: number, chatColour?: string): void; + sendShoutMessage(text: string, styleId: number, chatColour?: string): void; sendWhisperMessage(recipientName: string, text: string, styleId: number): void; sendChatTypingMessage(isTyping: boolean): void; + sendBackgroundMessage(backgroundImage: number, backgroundStand: number, backgroundOverlay: number, backgroundCard?: number): void; sendMottoMessage(motto: string): void; sendDanceMessage(danceId: number): void; sendExpressionMessage(expression: number): void; @@ -50,6 +51,7 @@ export interface IRoomSession sendScriptProceed(): void; userDataManager: IUserDataManager; roomId: number; + password: string; state: string; tradeMode: number; isPrivateRoom: boolean; From c37171a61c104282b71347665532ec5bbff37d5a Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 11 May 2026 21:09:22 +0200 Subject: [PATCH 06/30] TS 5.7+ ArrayBuffer drift: cast where ArrayBufferLike leaked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TypeScript 5.7 split ArrayBuffer / SharedArrayBuffer at the type level (ArrayBuffer now exposes resizable/transfer/detached etc; SharedArrayBuffer doesn't), and parametrized the typed-array constructors so plain Uint8Array became Uint8Array. The renderer never uses SharedArrayBuffer, so this is type-level only — narrowing back to ArrayBuffer at the boundaries: - BinaryReader.readBytes() / .toArrayBuffer() return the underlying DataView buffer; cast to ArrayBuffer. - BinaryWriter.getBuffer() same shape. - WsSessionCrypto.randomNonce() now returns Uint8Array (it's always backed by a plain ArrayBuffer); aesGcmEncrypt/Decrypt nonce parameter retyped accordingly so SubtleCrypto.encrypt accepts it as BufferSource. - ArrayBufferToBase64 now accepts Uint8Array | ArrayBufferLike directly (pako/inflate hands back Uint8Array which the old ArrayBuffer-only signature rejected). --- packages/communication/src/crypto/WsSessionCrypto.ts | 6 +++--- packages/utils/src/ArrayBufferToBase64.ts | 4 ++-- packages/utils/src/BinaryReader.ts | 4 ++-- packages/utils/src/BinaryWriter.ts | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/communication/src/crypto/WsSessionCrypto.ts b/packages/communication/src/crypto/WsSessionCrypto.ts index e92078c..ed7f814 100644 --- a/packages/communication/src/crypto/WsSessionCrypto.ts +++ b/packages/communication/src/crypto/WsSessionCrypto.ts @@ -38,17 +38,17 @@ export async function deriveAesKey(sharedSecret: ArrayBuffer): Promise +export async function aesGcmEncrypt(key: CryptoKey, nonce: Uint8Array, plaintext: ArrayBuffer): Promise { return window.crypto.subtle.encrypt({ name: 'AES-GCM', iv: nonce, tagLength: GCM_TAG_LEN * 8 }, key, plaintext); } -export async function aesGcmDecrypt(key: CryptoKey, nonce: Uint8Array, ciphertextWithTag: ArrayBuffer): Promise +export async function aesGcmDecrypt(key: CryptoKey, nonce: Uint8Array, ciphertextWithTag: ArrayBuffer): Promise { return window.crypto.subtle.decrypt({ name: 'AES-GCM', iv: nonce, tagLength: GCM_TAG_LEN * 8 }, key, ciphertextWithTag); } -export function randomNonce(): Uint8Array +export function randomNonce(): Uint8Array { const n = new Uint8Array(NONCE_LEN); window.crypto.getRandomValues(n); diff --git a/packages/utils/src/ArrayBufferToBase64.ts b/packages/utils/src/ArrayBufferToBase64.ts index aa8eb74..409bca0 100644 --- a/packages/utils/src/ArrayBufferToBase64.ts +++ b/packages/utils/src/ArrayBufferToBase64.ts @@ -1,8 +1,8 @@ -export const ArrayBufferToBase64 = (buffer: ArrayBuffer) => +export const ArrayBufferToBase64 = (buffer: ArrayBufferLike | Uint8Array) => { let binary = ''; - const bytes = new Uint8Array(buffer); + const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer); const len = bytes.byteLength; for(let i = 0; i < len; i++) (binary += String.fromCharCode(bytes[i])); diff --git a/packages/utils/src/BinaryReader.ts b/packages/utils/src/BinaryReader.ts index b0c68ac..f42b25f 100644 --- a/packages/utils/src/BinaryReader.ts +++ b/packages/utils/src/BinaryReader.ts @@ -13,7 +13,7 @@ export class BinaryReader implements IBinaryReader public readBytes(length: number): IBinaryReader { - const buffer = new BinaryReader(this._dataView.buffer.slice(this._position, this._position + length)); + const buffer = new BinaryReader(this._dataView.buffer.slice(this._position, this._position + length) as ArrayBuffer); this._position += length; @@ -77,6 +77,6 @@ export class BinaryReader implements IBinaryReader public toArrayBuffer(): ArrayBuffer { - return this._dataView.buffer; + return this._dataView.buffer as ArrayBuffer; } } diff --git a/packages/utils/src/BinaryWriter.ts b/packages/utils/src/BinaryWriter.ts index dd20ef5..554a823 100644 --- a/packages/utils/src/BinaryWriter.ts +++ b/packages/utils/src/BinaryWriter.ts @@ -89,7 +89,7 @@ export class BinaryWriter implements IBinaryWriter public getBuffer(): ArrayBuffer { - return this._buffer.buffer; + return this._buffer.buffer as ArrayBuffer; } public get position(): number From 08d1efafbe34de23be5f33d0fab1749162525c3a Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 11 May 2026 21:09:31 +0200 Subject: [PATCH 07/30] =?UTF-8?q?Drop=20dead=20sendWhisperGroupMessage=20?= =?UTF-8?q?=E2=80=94=20composer=20never=20existed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IRoomSession.sendWhisperGroupMessage(userId) was declared in the interface and implemented in RoomSession by sending 'new ChatWhisperGroupComposer(userId)' — but no such composer class exists in the renderer (the file was never created). The only whisper composer is RoomUnitChatWhisperComposer, which takes (recipientName, message, styleId), not a userId. No client call site references sendWhisperGroupMessage (grep across Nitro-V3/src returned zero hits). Removing the dead interface method + broken impl is safer than inventing a ChatWhisperGroupComposer class with no server-side handler. --- packages/api/src/nitro/session/IRoomSession.ts | 1 - packages/session/src/RoomSession.ts | 5 ----- 2 files changed, 6 deletions(-) diff --git a/packages/api/src/nitro/session/IRoomSession.ts b/packages/api/src/nitro/session/IRoomSession.ts index d12ad5a..cfd5f1f 100644 --- a/packages/api/src/nitro/session/IRoomSession.ts +++ b/packages/api/src/nitro/session/IRoomSession.ts @@ -21,7 +21,6 @@ export interface IRoomSession sendPostureMessage(posture: number): void; sendDoorbellApprovalMessage(userName: string, flag: boolean): void; sendAmbassadorAlertMessage(userId: number): void; - sendWhisperGroupMessage(userId: number): void; sendKickMessage(userId: number): void; sendMuteMessage(userId: number, minutes: number): void; sendBanMessage(userId: number, type: string): void; diff --git a/packages/session/src/RoomSession.ts b/packages/session/src/RoomSession.ts index 3e70501..6b0af0c 100644 --- a/packages/session/src/RoomSession.ts +++ b/packages/session/src/RoomSession.ts @@ -138,11 +138,6 @@ export class RoomSession implements IRoomSession { GetCommunication().connection.send(new RoomAmbassadorAlertComposer(userId)); } - - public sendWhisperGroupMessage(userId: number): void - { - GetCommunication().connection.send(new ChatWhisperGroupComposer(userId)); - } public sendKickMessage(userId: number): void { From 0fc38a1c7139915180aae159000345da59ba7363 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 11 May 2026 21:09:37 +0200 Subject: [PATCH 08/30] Fix self-referential ConstructorParameters in two Wired composers WiredRoomSettingsRequestComposer and WiredUserVariablesRequestComposer declared 'implements IMessageComposer>' but neither defines a constructor, so ConstructorParameters resolved to 'any[]' and getMessageArray() returning [] (any[]) failed the narrower base-type signature () => []. Both composers send zero payload; type as IMessageComposer<[]> directly + annotate the return type. --- .../outgoing/roomevents/WiredRoomSettingsRequestComposer.ts | 4 ++-- .../outgoing/roomevents/WiredUserVariablesRequestComposer.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/communication/src/messages/outgoing/roomevents/WiredRoomSettingsRequestComposer.ts b/packages/communication/src/messages/outgoing/roomevents/WiredRoomSettingsRequestComposer.ts index c9f6948..7c67242 100644 --- a/packages/communication/src/messages/outgoing/roomevents/WiredRoomSettingsRequestComposer.ts +++ b/packages/communication/src/messages/outgoing/roomevents/WiredRoomSettingsRequestComposer.ts @@ -1,8 +1,8 @@ import { IMessageComposer } from '@nitrots/api'; -export class WiredRoomSettingsRequestComposer implements IMessageComposer> +export class WiredRoomSettingsRequestComposer implements IMessageComposer<[]> { - public getMessageArray() + public getMessageArray(): [] { return []; } diff --git a/packages/communication/src/messages/outgoing/roomevents/WiredUserVariablesRequestComposer.ts b/packages/communication/src/messages/outgoing/roomevents/WiredUserVariablesRequestComposer.ts index 83c9733..db6ab76 100644 --- a/packages/communication/src/messages/outgoing/roomevents/WiredUserVariablesRequestComposer.ts +++ b/packages/communication/src/messages/outgoing/roomevents/WiredUserVariablesRequestComposer.ts @@ -1,8 +1,8 @@ import { IMessageComposer } from '@nitrots/api'; -export class WiredUserVariablesRequestComposer implements IMessageComposer> +export class WiredUserVariablesRequestComposer implements IMessageComposer<[]> { - public getMessageArray() + public getMessageArray(): [] { return []; } From 999b8187d663e314c9a4a959de1b89605a2d57b5 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 11 May 2026 21:09:41 +0200 Subject: [PATCH 09/30] Fix PetBreedingMessageParser bytesAvailable check bytesAvailable is a boolean (IMessageDataWrapper.bytesAvailable: boolean, returns 'there is at least one byte left'); the parser was doing 'wrapper.bytesAvailable < 12' as if it were a count, which both mis-compares boolean to number and short-circuits incorrectly when exactly 11 bytes remain. Align with every other parser in the codebase: 'if(!wrapper || !wrapper.bytesAvailable) return false;'. The downstream readInt calls already throw on truncated packets so the explicit length check was load-bearing only against malformed inputs that wouldn't parse anyway. --- .../inventory/pets/PetBreedingMessageParser.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/communication/src/messages/parser/inventory/pets/PetBreedingMessageParser.ts b/packages/communication/src/messages/parser/inventory/pets/PetBreedingMessageParser.ts index 7a2a576..8eed7d3 100644 --- a/packages/communication/src/messages/parser/inventory/pets/PetBreedingMessageParser.ts +++ b/packages/communication/src/messages/parser/inventory/pets/PetBreedingMessageParser.ts @@ -19,17 +19,16 @@ export class PetBreedingMessageParser implements IMessageParser return true; } - public parse(wrapper: IMessageDataWrapper): boolean { - if (!wrapper || wrapper.bytesAvailable < 12) { - return false; - } + public parse(wrapper: IMessageDataWrapper): boolean + { + if(!wrapper || !wrapper.bytesAvailable) return false; - this._state = wrapper.readInt(); - this._ownPetId = wrapper.readInt(); - this._otherPetId = wrapper.readInt(); + this._state = wrapper.readInt(); + this._ownPetId = wrapper.readInt(); + this._otherPetId = wrapper.readInt(); - return true; - } + return true; + } public get state(): number { From b42f989e280d65d437355f4173b56526a01f9473 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 11 May 2026 21:09:51 +0200 Subject: [PATCH 10/30] RoomEnterComposer: optional spawnX/spawnY for reconnect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Arcturus' RequestRoomLoadEvent reads the two extra ints only when the inbound packet has 8+ bytes remaining after roomId+password, so the renderer can send 2-arg or 4-arg payloads against the same header. The client already calls 'new RoomEnterComposer(roomId, password, spawnX, spawnY)' in two places inside RoomSession / RoomSessionManager (the reconnect/respawn flow) — the composer signature is what was lagging behind. Server-side reference: Arcturus-Morningstar-Extended/Emulator/src/main/java/com/eu/habbo/ messages/incoming/rooms/RequestRoomLoadEvent.java --- .../outgoing/room/access/RoomEnterComposer.ts | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/communication/src/messages/outgoing/room/access/RoomEnterComposer.ts b/packages/communication/src/messages/outgoing/room/access/RoomEnterComposer.ts index a195223..3c29e4b 100644 --- a/packages/communication/src/messages/outgoing/room/access/RoomEnterComposer.ts +++ b/packages/communication/src/messages/outgoing/room/access/RoomEnterComposer.ts @@ -1,12 +1,23 @@ import { IMessageComposer } from '@nitrots/api'; -export class RoomEnterComposer implements IMessageComposer> -{ - private _data: ConstructorParameters; +type RoomEnterPayload = [ number, string, number?, number? ]; - constructor(roomId: number, password: string = null) +export class RoomEnterComposer implements IMessageComposer +{ + private _data: RoomEnterPayload; + + /** + * Optional spawnX/spawnY let the server resume the avatar at a + * specific tile when re-entering the same room — used by the + * reconnect flow. Arcturus' RequestRoomLoadEvent reads both ints + * only if `packet.remaining >= 8`, so omitting them keeps the + * legacy enter-via-door behavior. + */ + constructor(roomId: number, password: string = null, spawnX?: number, spawnY?: number) { - this._data = [roomId, password]; + this._data = (spawnX !== undefined && spawnY !== undefined) + ? [ roomId, password, spawnX, spawnY ] + : [ roomId, password ]; } public getMessageArray() From 5ea3201e31aec8da4ff7696093e25a7508d90063 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 11 May 2026 21:09:59 +0200 Subject: [PATCH 11/30] Align with Pixi v8: Filter[] union, WebGLRenderer narrow, ImageLike MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four sites where Pixi v8's stricter typing tripped tsgo: - AvatarImage: container.filters is typed as 'readonly Filter[] | null' in v8 (no longer a single-Filter union). The old fallback branch 'else container.filters = [container.filters, …]' tried to treat a readonly array as a single Filter; collapsed to the array-spread path which now covers both undefined and non-empty cases. Added Filter to the pixi.js import. - FurnitureBadgeDisplayVisualization.updateSprite() had a 4-arg override (sprite, asset, scale, layerId) of the parent's 2-arg signature (scale, layerId). The sprite/asset were never used from the parameters — the body only mutated 'sprite'. Refactored to fetch the sprite via this.getSprite(layerId) inside the override body so the signature matches the base. - ExtendedSprite: 'renderer.gl' / 'glRenderTarget.resolveTargetFramebuffer' exist only on WebGLRenderer / GlRenderTarget (not the WebGPU variants). The runtime check 'renderer.type === RendererType.WEBGL' guarantees this; cast at the boundary to satisfy the typechecker. - TextureUtils.generateImage: Pixi v8's Extractor.image() returns the union ImageLike (HTMLCanvasElement | HTMLImageElement); the public signature promises HTMLImageElement. Cast at return — the Pixi default backend returns HTMLImageElement here. --- packages/avatar/src/AvatarImage.ts | 13 +++++-------- .../furniture/FurnitureBadgeDisplayVisualization.ts | 8 ++++++-- packages/room/src/renderer/utils/ExtendedSprite.ts | 9 +++++---- packages/utils/src/TextureUtils.ts | 2 +- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/avatar/src/AvatarImage.ts b/packages/avatar/src/AvatarImage.ts index b34d1e5..5d23c84 100644 --- a/packages/avatar/src/AvatarImage.ts +++ b/packages/avatar/src/AvatarImage.ts @@ -1,6 +1,6 @@ -import { AvatarAction, AvatarDirectionAngle, AvatarScaleType, AvatarSetType, IActiveActionData, IAnimationLayerData, IAvatarDataContainer, IAvatarEffectListener, IAvatarFigureContainer, IAvatarImage, IPartColor, ISpriteDataContainer } from '@nitrots/api'; +import { AvatarAction, AvatarDirectionAngle, AvatarScaleType, AvatarSetType, IActiveActionData, IAnimationLayerData, IAvatarDataContainer, IAvatarEffectListener, IAvatarFigureContainer, IAvatarImage, IGraphicAsset, IPartColor, ISpriteDataContainer } from '@nitrots/api'; import { GetRenderer, GetTexturePool, GetTickerTime, PaletteMapFilter, TextureUtils } from '@nitrots/utils'; -import { ColorMatrixFilter, Container, RenderTexture, Sprite, Texture } from 'pixi.js'; +import { ColorMatrixFilter, Container, Filter, RenderTexture, Sprite, Texture } from 'pixi.js'; import { AvatarFigureContainer } from './AvatarFigureContainer'; import { AvatarStructure } from './AvatarStructure'; import { EffectAssetDownloadManager } from './EffectAssetDownloadManager'; @@ -243,8 +243,7 @@ export class AvatarImage implements IAvatarImage, IAvatarEffectListener if(this._avatarSpriteData.colorTransform) { if(container.filters === undefined || container.filters === null) container.filters = [ this._avatarSpriteData.colorTransform ]; - else if(Array.isArray(container.filters)) container.filters = [ ...container.filters, this._avatarSpriteData.colorTransform ]; - else container.filters = [ container.filters, this._avatarSpriteData.colorTransform ]; + else container.filters = [ ...(container.filters as readonly Filter[]), this._avatarSpriteData.colorTransform ]; } if(this._avatarSpriteData.paletteIsGrayscale) @@ -257,8 +256,7 @@ export class AvatarImage implements IAvatarImage, IAvatarEffectListener }); if(container.filters === undefined || container.filters === null) container.filters = [ paletteMapFilter ]; - else if(Array.isArray(container.filters)) container.filters = [ ...container.filters, paletteMapFilter ]; - else container.filters = [ container.filters, paletteMapFilter ]; + else container.filters = [ ...(container.filters as readonly Filter[]), paletteMapFilter ]; } } @@ -766,8 +764,7 @@ export class AvatarImage implements IAvatarImage, IAvatarEffectListener ]; if(container.filters === undefined || container.filters === null) container.filters = [ filter ]; - else if(Array.isArray(container.filters)) container.filters = [ ...container.filters, filter ]; - else container.filters = [ container.filters, filter ]; + else container.filters = [ ...(container.filters as readonly Filter[]), filter ]; return container; } diff --git a/packages/room/src/object/visualization/furniture/FurnitureBadgeDisplayVisualization.ts b/packages/room/src/object/visualization/furniture/FurnitureBadgeDisplayVisualization.ts index 7e344e8..87c6e2e 100644 --- a/packages/room/src/object/visualization/furniture/FurnitureBadgeDisplayVisualization.ts +++ b/packages/room/src/object/visualization/furniture/FurnitureBadgeDisplayVisualization.ts @@ -170,14 +170,18 @@ export class FurnitureBadgeDisplayVisualization extends FurnitureAnimatedVisuali return assetName; } - protected updateSprite(sprite: IRoomObjectSprite, asset: IGraphicAsset, scale: number, layerId: number): void + protected updateSprite(scale: number, layerId: number): void { - super.updateSprite(sprite, asset, scale, layerId); + super.updateSprite(scale, layerId); const tag = this.getLayerTag(scale, this.direction, layerId); if(tag === FurnitureBadgeDisplayVisualization.BADGE_TAG) { + const sprite = this.getSprite(layerId); + + if(!sprite) return; + sprite.visible = true; sprite.alpha = 255; sprite.color = 0xFFFFFF; diff --git a/packages/room/src/renderer/utils/ExtendedSprite.ts b/packages/room/src/renderer/utils/ExtendedSprite.ts index f3b08be..3c08ae0 100644 --- a/packages/room/src/renderer/utils/ExtendedSprite.ts +++ b/packages/room/src/renderer/utils/ExtendedSprite.ts @@ -1,6 +1,6 @@ import { AlphaTolerance } from '@nitrots/api'; import { GetRenderer, TextureUtils } from '@nitrots/utils'; -import { Point, RendererType, Sprite, Texture, TextureSource, WebGPURenderer } from 'pixi.js'; +import { GlRenderTarget, Point, RendererType, Sprite, Texture, TextureSource, WebGLRenderer, WebGPURenderer } from 'pixi.js'; const BYTES_PER_PIXEL = 4; @@ -97,10 +97,11 @@ export class ExtendedSprite extends Sprite { pixels = new Uint8ClampedArray(BYTES_PER_PIXEL * width * height); - const renderTarget = renderer.renderTarget.getRenderTarget(textureSource); - const glRenderTarget = renderer.renderTarget.getGpuRenderTarget(renderTarget); + const webglRenderer = renderer as WebGLRenderer; + const renderTarget = webglRenderer.renderTarget.getRenderTarget(textureSource); + const glRenderTarget = webglRenderer.renderTarget.getGpuRenderTarget(renderTarget) as GlRenderTarget; - const gl = renderer.gl; + const gl = webglRenderer.gl; gl.bindFramebuffer(gl.FRAMEBUFFER, glRenderTarget.resolveTargetFramebuffer); diff --git a/packages/utils/src/TextureUtils.ts b/packages/utils/src/TextureUtils.ts index 0b12996..4af952c 100644 --- a/packages/utils/src/TextureUtils.ts +++ b/packages/utils/src/TextureUtils.ts @@ -28,7 +28,7 @@ export class TextureUtils try { - return await this.getExtractor().image(options); + return await this.getExtractor().image(options) as HTMLImageElement; } catch(e) { From 22d4e5bfb0346ef63d26ee1dee7832ece06f4238 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 11 May 2026 21:10:04 +0200 Subject: [PATCH 12/30] SocketConnection parser cast + RoomChatHandler arg-order fix - SocketConnection.processMessage() did 'new events[0].parserClass()' where parserClass is typed as 'Function' on IMessageEvent (no construct signature). Cast to 'new () => IMessageParser' at the call site so the spawned instance is type-correct downstream. - RoomChatHandler dispatched RoomSessionChatEvent with the args in the wrong order: '[]' (intended as the 'links' array) was landing in the 'chatColours' string slot. Swap to '"", []' so links go to position 8 and chatColours stays a string. --- packages/communication/src/SocketConnection.ts | 4 ++-- packages/session/src/handler/RoomChatHandler.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/communication/src/SocketConnection.ts b/packages/communication/src/SocketConnection.ts index f0027fe..87ab694 100644 --- a/packages/communication/src/SocketConnection.ts +++ b/packages/communication/src/SocketConnection.ts @@ -1,4 +1,4 @@ -import { ICodec, IConnection, IMessageComposer, IMessageConfiguration, IMessageDataWrapper, IMessageEvent, WebSocketEventEnum } from '@nitrots/api'; +import { ICodec, IConnection, IMessageComposer, IMessageConfiguration, IMessageDataWrapper, IMessageEvent, IMessageParser, WebSocketEventEnum } from '@nitrots/api'; import { GetConfiguration } from '@nitrots/configuration'; import { GetEventDispatcher, NitroEvent, NitroEventType, ReconnectEvent } from '@nitrots/events'; import { NitroLogger } from '@nitrots/utils'; @@ -509,7 +509,7 @@ export class SocketConnection implements IConnection try { - const parser = new events[0].parserClass(); + const parser = new (events[0].parserClass as new () => IMessageParser)(); if(!parser || !parser.flush() || !parser.parse(wrapper)) return null; diff --git a/packages/session/src/handler/RoomChatHandler.ts b/packages/session/src/handler/RoomChatHandler.ts index f901ef3..c7b9278 100644 --- a/packages/session/src/handler/RoomChatHandler.ts +++ b/packages/session/src/handler/RoomChatHandler.ts @@ -168,6 +168,6 @@ export class RoomChatHandler extends BaseHandler if(!parser) return; - GetEventDispatcher().dispatchEvent(new RoomSessionChatEvent(RoomSessionChatEvent.CHAT_EVENT, session, session.ownRoomIndex, '', RoomSessionChatEvent.CHAT_TYPE_MUTE_REMAINING, SystemChatStyleEnum.GENERIC, [], null, parser.seconds)); + GetEventDispatcher().dispatchEvent(new RoomSessionChatEvent(RoomSessionChatEvent.CHAT_EVENT, session, session.ownRoomIndex, '', RoomSessionChatEvent.CHAT_TYPE_MUTE_REMAINING, SystemChatStyleEnum.GENERIC, '', [], parser.seconds)); } } From f7a58972321c5e079a163b04a9a52299a7fcf5e7 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 11 May 2026 21:34:47 +0200 Subject: [PATCH 13/30] Renderer: align NitroConfig Window decl with client + fix glob .default access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two tsgo nits that propagate to the client when the renderer is linked in: - packages/utils/src/NitroConfig.ts declared 'NitroConfig?: { [index: string]: any }' on Window, but Nitro-V3 declares 'NitroConfig?: Record' in its react-app-env.d.ts. The two declaration-merging fragments must match — switching the renderer side to Record unifies them. - packages/assets/src/AssetManager.ts: 'import.meta.glob(...)' is augmented as Record in the client's typedef, so 'mod.default ?? mod' (defensive handling of either string or { default: string }) failed because mod is typed string. Cast inline: '((mod as { default?: string }).default ?? mod)'. Renderer tsgo error count: 3 -> 0. --- packages/assets/src/AssetManager.ts | 4 ++-- packages/utils/src/NitroConfig.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/assets/src/AssetManager.ts b/packages/assets/src/AssetManager.ts index ae27cbf..7d992f7 100644 --- a/packages/assets/src/AssetManager.ts +++ b/packages/assets/src/AssetManager.ts @@ -227,7 +227,7 @@ export class AssetManager implements IAssetManager for(const path in merged) { const mod = merged[path]; - const imageUrl = (mod.default ?? mod) as string; + const imageUrl = ((mod as { default?: string }).default ?? mod) as string; const file = path.split('/').pop()!; const rawName = file.replace(/\.png$/i, ''); @@ -296,7 +296,7 @@ export class AssetManager implements IAssetManager if(!path.startsWith(prefix)) continue; const mod = allImages[path]; - const imageUrl = (mod.default ?? mod) as string; + const imageUrl = ((mod as { default?: string }).default ?? mod) as string; const file = path.split('/').pop()!; const rawName = file.replace(/\.png$/i, ''); diff --git a/packages/utils/src/NitroConfig.ts b/packages/utils/src/NitroConfig.ts index aa905eb..aca544a 100644 --- a/packages/utils/src/NitroConfig.ts +++ b/packages/utils/src/NitroConfig.ts @@ -2,8 +2,8 @@ export { }; declare global { - interface Window - { - NitroConfig?: { [index: string]: any }; - } + interface Window + { + NitroConfig?: Record; + } } From ef6c66105851fddbf7f9a686f64a14eb8b74d93c Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 11 May 2026 21:46:36 +0200 Subject: [PATCH 14/30] Renderer: surface allowUnderpass on RoomSettingsData + composer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Arcturus' RoomSettingsComposer appends an extra int at the end of the payload — room.isAllowUnderpass() ? 1 : 0 — and RoomSettingsSaveEvent optionally reads back a boolean at the end (if bytesAvailable > 0). The renderer side never modeled this trailing field, so the client couldn't surface or persist it. - RoomSettingsData: add _allowUnderpass field + getter/setter + propagation through the .from() copy. - RoomSettingsDataParser: read one trailing int after the moderation settings, guarded by 'if(wrapper.bytesAvailable)' so older servers that don't emit it keep parsing cleanly. - SaveRoomSettingsComposer: optional trailing allowUnderpass arg. The server's optional-read guard tolerates 24-arg or 25-arg payloads, so callers that don't care about the field still send the legacy shape. Cross-repo reference points: - Arcturus emit side: Emulator/src/main/java/com/eu/habbo/messages/ outgoing/rooms/RoomSettingsComposer.java line 55. - Arcturus read side: Emulator/src/main/java/com/eu/habbo/messages/ incoming/rooms/RoomSettingsSaveEvent.java lines 133-135. Net client tsgo error count: 3 -> 0 on the NavigatorRoomSettings cluster. --- .../outgoing/room/data/SaveRoomSettingsComposer.ts | 5 ++++- .../messages/parser/roomsettings/RoomSettingsData.ts | 12 ++++++++++++ .../parser/roomsettings/RoomSettingsDataParser.ts | 4 ++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/communication/src/messages/outgoing/room/data/SaveRoomSettingsComposer.ts b/packages/communication/src/messages/outgoing/room/data/SaveRoomSettingsComposer.ts index 8b69492..a941b60 100644 --- a/packages/communication/src/messages/outgoing/room/data/SaveRoomSettingsComposer.ts +++ b/packages/communication/src/messages/outgoing/room/data/SaveRoomSettingsComposer.ts @@ -32,7 +32,8 @@ implements chatBubbleWeight: number, chatBubbleSpeed: number, chatDistance: number, - chatFloodProtection: number + chatFloodProtection: number, + allowUnderpass?: boolean ) { //@ts-ignore @@ -67,6 +68,8 @@ implements chatDistance, chatFloodProtection ); + + if(allowUnderpass !== undefined) this._data.push(allowUnderpass); } public getMessageArray() diff --git a/packages/communication/src/messages/parser/roomsettings/RoomSettingsData.ts b/packages/communication/src/messages/parser/roomsettings/RoomSettingsData.ts index 4384e5e..c558d17 100644 --- a/packages/communication/src/messages/parser/roomsettings/RoomSettingsData.ts +++ b/packages/communication/src/messages/parser/roomsettings/RoomSettingsData.ts @@ -37,6 +37,7 @@ export class RoomSettingsData private _roomModerationSettings: RoomModerationSettings = null; private _chatSettings: RoomChatSettings = null; private _allowNavigatorDynamicCats: boolean = false; + private _allowUnderpass: boolean = false; public static from(settings: RoomSettingsData) { @@ -65,6 +66,7 @@ export class RoomSettingsData instance._roomModerationSettings = settings._roomModerationSettings; instance._chatSettings = settings._chatSettings; instance._allowNavigatorDynamicCats = settings._allowNavigatorDynamicCats; + instance._allowUnderpass = settings._allowUnderpass; return instance; } @@ -329,4 +331,14 @@ export class RoomSettingsData { this._allowNavigatorDynamicCats = flag; } + + public get allowUnderpass(): boolean + { + return this._allowUnderpass; + } + + public set allowUnderpass(flag: boolean) + { + this._allowUnderpass = flag; + } } diff --git a/packages/communication/src/messages/parser/roomsettings/RoomSettingsDataParser.ts b/packages/communication/src/messages/parser/roomsettings/RoomSettingsDataParser.ts index ed02587..45a9650 100644 --- a/packages/communication/src/messages/parser/roomsettings/RoomSettingsDataParser.ts +++ b/packages/communication/src/messages/parser/roomsettings/RoomSettingsDataParser.ts @@ -49,6 +49,10 @@ export class RoomSettingsDataParser implements IMessageParser this._roomSettingsData.allowNavigatorDynamicCats = wrapper.readBoolean(); this._roomSettingsData.roomModerationSettings = new RoomModerationSettings(wrapper); + // Custom Arcturus extension: trailing int (0/1) for the underpass toggle. + // Older servers may not emit it; default stays false when absent. + if(wrapper.bytesAvailable) this._roomSettingsData.allowUnderpass = (wrapper.readInt() === 1); + return true; } From 5f5ba2fad3aafb2bcbb137d89beb95cd26d88068 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 11 May 2026 23:14:04 +0200 Subject: [PATCH 15/30] docs(claude): document recent feat/react19-event-bus additions Adds a 'Recent renderer changes' section to CLAUDE.md covering all the non-v2.1.0 work that landed during the React 19 modernization round: - RoomEnterComposer optional spawnX/spawnY (matches Arcturus' RequestRoomLoadEvent optional tail). - RoomSettingsData.allowUnderpass field + parser tail-int + composer optional arg (Arcturus already emits the int). - Dropped dead sendWhisperGroupMessage / ChatWhisperGroupComposer. - TS 5.7+ ArrayBuffer drift handling and Pixi v8 narrows (FurnitureBadgeDisplayVisualization signature realignment, WebGLRenderer cast in ExtendedSprite, Filter[] union in AvatarImage, ImageLike cast in TextureUtils, NitroConfig Window-decl unification, empty-tuple composers). - PetBreedingMessageParser bytesAvailable bool-vs-number bug fix. Also adds two gotchas: 'bytesAvailable is a boolean' (was hit by PetBreeding) and 'composer getMessageArray return type must match the type argument' (was hit by both Wired*RequestComposer). --- CLAUDE.md | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index ed82000..cfca37b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,6 +79,74 @@ The interface contracts live in: - `packages/api/src/nitro/session/IUserDataSnapshot.ts` - `packages/api/src/nitro/session/IRoomSessionSnapshot.ts` +## Recent renderer changes (`feat/react19-event-bus`) + +Tracked separately from the v2.1.0 batch above; all are +non-breaking additions or align-with-Arcturus fixes: + +### RoomEnterComposer: optional `spawnX` / `spawnY` + +`new RoomEnterComposer(roomId, password?, spawnX?, spawnY?)`. The +Arcturus `RequestRoomLoadEvent` handler reads the two extra ints only +when `packet.remaining >= 8`, so the same composer header serves both +the legacy 2-arg form (door spawn) and the 4-arg form (reconnect / +respawn at a specific tile). RoomSession + RoomSessionManager use the +4-arg variant in their `enterRoom` / reconnect paths. + +### RoomSettingsData: `allowUnderpass` field + +`RoomSettingsData` (and its parser) now exposes `allowUnderpass: +boolean`. Arcturus' `RoomSettingsComposer` already appends one +trailing int for this flag, and the new parser reads it via +`if(wrapper.bytesAvailable) … readInt() === 1` so older servers that +don't emit the field still parse cleanly. `SaveRoomSettingsComposer` +accepts an optional `allowUnderpass` arg at the end of its parameter +list; the server-side `RoomSettingsSaveEvent` reads it under +`packet.bytesAvailable() > 0`. + +### Dropped dead code: `sendWhisperGroupMessage` + +`IRoomSession.sendWhisperGroupMessage(userId)` referenced a +`ChatWhisperGroupComposer` that never existed in the codebase and had +zero call sites in the React client. Both the interface declaration +and the broken impl are removed. The real whisper path is +`RoomUnitChatWhisperComposer(recipientName, message, styleId)` — +unchanged. + +### TS 5.7+ and Pixi v8 alignment + +- `ArrayBufferLike` drift handled with explicit casts in `BinaryReader` + / `BinaryWriter` / `WsSessionCrypto.randomNonce()` / + `ArrayBufferToBase64`. The renderer never uses SharedArrayBuffer, so + these are type-level narrowings only. +- `Container.filters` in Pixi v8 is `Filter[] | readonly Filter[] | null`; + the AvatarImage filter-stack mutation always goes through the + spread-array branch now (no single-Filter fallback). `Filter` is + imported explicitly from pixi.js. +- `ExtendedSprite` casts the renderer to `WebGLRenderer` inside the + `RendererType.WEBGL` branch so `renderer.gl` / + `glRenderTarget.resolveTargetFramebuffer` resolve. +- `FurnitureBadgeDisplayVisualization.updateSprite` signature realigned + to the parent's 2-arg `(scale, layerId)` shape (was a custom 4-arg + override that broke base-class assignability). +- `TextureUtils.generateImage` casts the extractor's `ImageLike` + union return to `HTMLImageElement` (the default backend produces + one). +- `Window.NitroConfig` declaration in `NitroConfig.ts` realigned to + the client's `Record` type so the merged decls + agree. +- Empty-tuple composers (`WiredRoomSettingsRequestComposer`, + `WiredUserVariablesRequestComposer`) annotate the return type + `(): []` explicitly so `IMessageComposer<[]>` lines up. + +### Bug fix: `PetBreedingMessageParser.bytesAvailable < 12` + +`bytesAvailable` is a boolean (the wrapper just answers "is there +anything left?"). The pet-breeding parser used to compare it against +`12` as if it were a byte count, which TS 6 caught and which was +also semantically wrong. Replaced with the standard +`if(!wrapper || !wrapper.bytesAvailable) return false;` guard. + ## Scripts ``` @@ -109,6 +177,15 @@ for the React-side bridge code. - **`SessionDataManager.getUserData(id)` does NOT exist.** Some legacy code in the React client used it under a `getUserData ?` truthy guard; the branch was always dead. Only `getUserDataSnapshot()` exists. +- **`bytesAvailable` is a boolean.** The codebase historically had one + parser (`PetBreedingMessageParser`) that compared it against a + number — fixed. The wrapper returns "any bytes left?", not a count. + Use it as a truthy guard or follow with `try {} catch` if you need + optional reads. +- **Composer `getMessageArray()` return type must match the type + argument.** `IMessageComposer<[]>` means the function returns `[]`, + not `any[]`. The two `Wired*RequestComposer`s that ship empty + payloads each annotate `getMessageArray(): []` explicitly. - `IRoomSession.sendChatMessage` / `sendShoutMessage` accept an optional `chatColour` 3rd arg (was required pre-2.1.1, now optional to match the historical call sites in the React client). The implementation From 98662e73998dbdcefa5913f10cd023ada715b197 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 18 May 2026 20:42:17 +0200 Subject: [PATCH 16/30] test(utils): add BinaryReader / BinaryWriter round-trip coverage (23 cases) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cover every public method on the binary pair, plus the typical packet shape (header + mixed payload) the composer/parser pipeline emits: - byte / short / int round-trips, including signed-edge values (int8 -1 from 0xFF, int16 / int32 boundaries) - big-endian wire-order assertions on writeShort / writeInt (matches Arcturus's DataInputStream) - string round-trip with length prefix + bare (includeLength=false) + UTF-8 multibyte byte count + empty-string edge - writeBytes for both number[] and ArrayBuffer payloads - readBytes slice returns an independent reader whose position is decoupled from the outer reader - remaining() decrements correctly across mixed-size reads - readFloat / readDouble decode IEEE-754 big-endian values (the writer has no float/double counterparts — buffer is built via DataView for these cases) - writer position getter + explicit setter (caller-managed reposition) - two independent writers concatenate cleanly into a single reader Suite: 127/127 (was 104/104). typecheck clean. --- .../utils/src/__tests__/BinaryReader.test.ts | 325 ++++++++++++++++++ 1 file changed, 325 insertions(+) create mode 100644 packages/utils/src/__tests__/BinaryReader.test.ts diff --git a/packages/utils/src/__tests__/BinaryReader.test.ts b/packages/utils/src/__tests__/BinaryReader.test.ts new file mode 100644 index 0000000..e4ddd4a --- /dev/null +++ b/packages/utils/src/__tests__/BinaryReader.test.ts @@ -0,0 +1,325 @@ +import { beforeEach, describe, expect, it } from 'vitest'; +import { BinaryReader } from '../BinaryReader'; +import { BinaryWriter } from '../BinaryWriter'; + +const concatBuffers = (...parts: ArrayBuffer[]): ArrayBuffer => +{ + const total = parts.reduce((sum, part) => sum + part.byteLength, 0); + const out = new Uint8Array(total); + let offset = 0; + + for(const part of parts) + { + out.set(new Uint8Array(part), offset); + offset += part.byteLength; + } + + return out.buffer; +}; + +describe('BinaryReader / BinaryWriter', () => +{ + let writer: BinaryWriter; + + beforeEach(() => + { + writer = new BinaryWriter(); + }); + + describe('byte round-trip', () => + { + it('writes and reads a single byte', () => + { + writer.writeByte(0x42); + + const reader = new BinaryReader(writer.getBuffer()); + + expect(reader.readByte()).toBe(0x42); + expect(reader.remaining()).toBe(0); + }); + + it('readByte returns a signed int8 (values above 127 wrap negative)', () => + { + writer.writeByte(0xFF); + + const reader = new BinaryReader(writer.getBuffer()); + + expect(reader.readByte()).toBe(-1); + }); + + it('writeByte chains', () => + { + writer.writeByte(1).writeByte(2).writeByte(3); + + const reader = new BinaryReader(writer.getBuffer()); + + expect(reader.readByte()).toBe(1); + expect(reader.readByte()).toBe(2); + expect(reader.readByte()).toBe(3); + }); + }); + + describe('short round-trip (16-bit big-endian)', () => + { + it('writes and reads a positive short', () => + { + writer.writeShort(0x1234); + + const reader = new BinaryReader(writer.getBuffer()); + + expect(reader.readShort()).toBe(0x1234); + }); + + it('round-trips the int16 boundary values', () => + { + writer.writeShort(32767).writeShort(-1); + + const reader = new BinaryReader(writer.getBuffer()); + + expect(reader.readShort()).toBe(32767); + expect(reader.readShort()).toBe(-1); + }); + + it('emits big-endian byte order', () => + { + writer.writeShort(0x0102); + + const bytes = new Uint8Array(writer.getBuffer()); + + expect(bytes[0]).toBe(0x01); + expect(bytes[1]).toBe(0x02); + }); + }); + + describe('int round-trip (32-bit big-endian)', () => + { + it('writes and reads a positive int', () => + { + writer.writeInt(123456789); + + const reader = new BinaryReader(writer.getBuffer()); + + expect(reader.readInt()).toBe(123456789); + }); + + it('round-trips the int32 boundaries (max / min / -1)', () => + { + writer.writeInt(2147483647).writeInt(-2147483648).writeInt(-1); + + const reader = new BinaryReader(writer.getBuffer()); + + expect(reader.readInt()).toBe(2147483647); + expect(reader.readInt()).toBe(-2147483648); + expect(reader.readInt()).toBe(-1); + }); + + it('emits big-endian byte order', () => + { + writer.writeInt(0x01020304); + + const bytes = new Uint8Array(writer.getBuffer()); + + expect(bytes[0]).toBe(0x01); + expect(bytes[1]).toBe(0x02); + expect(bytes[2]).toBe(0x03); + expect(bytes[3]).toBe(0x04); + }); + }); + + describe('string round-trip', () => + { + it('writes a length-prefixed string and decodes it back via readShort + readBytes', () => + { + writer.writeString('hello'); + + const reader = new BinaryReader(writer.getBuffer()); + const length = reader.readShort(); + + expect(length).toBe(5); + expect(reader.readBytes(length).toString()).toBe('hello'); + }); + + it('round-trips UTF-8 multibyte characters with correct byte length', () => + { + // 'café' = 5 bytes UTF-8 (c, a, 0xC3 0xA9, ASCII finale) + writer.writeString('café'); + + const reader = new BinaryReader(writer.getBuffer()); + const length = reader.readShort(); + + expect(length).toBe(5); + expect(reader.readBytes(length).toString()).toBe('café'); + }); + + it('writeString with includeLength=false omits the length prefix', () => + { + writer.writeString('xy', false); + + const buf = writer.getBuffer(); + + expect(buf.byteLength).toBe(2); + expect(new Uint8Array(buf)[0]).toBe(0x78); // 'x' + expect(new Uint8Array(buf)[1]).toBe(0x79); // 'y' + }); + + it('round-trips the empty string', () => + { + writer.writeString(''); + + const reader = new BinaryReader(writer.getBuffer()); + + expect(reader.readShort()).toBe(0); + expect(reader.remaining()).toBe(0); + }); + }); + + describe('writeBytes', () => + { + it('appends a number[] payload', () => + { + writer.writeBytes([ 0x10, 0x20, 0x30 ]); + + const reader = new BinaryReader(writer.getBuffer()); + + expect(reader.readByte()).toBe(0x10); + expect(reader.readByte()).toBe(0x20); + expect(reader.readByte()).toBe(0x30); + }); + + it('appends an ArrayBuffer payload', () => + { + const payload = new Uint8Array([ 0xAA, 0xBB ]).buffer; + + writer.writeBytes(payload); + + const reader = new BinaryReader(writer.getBuffer()); + + expect(reader.readByte()).toBe(-86); // 0xAA as int8 + expect(reader.readByte()).toBe(-69); // 0xBB as int8 + }); + }); + + describe('readBytes slice', () => + { + it('returns an independent reader over the requested slice', () => + { + writer.writeInt(0xCAFEBABE | 0).writeInt(0xDEADBEEF | 0); + + const reader = new BinaryReader(writer.getBuffer()); + const sliced = reader.readBytes(4); + + // The slice's position is independent of the outer reader. + expect(sliced.readInt()).toBe(0xCAFEBABE | 0); + // The outer reader advanced by 4 and can still read the second int. + expect(reader.readInt()).toBe(0xDEADBEEF | 0); + }); + }); + + describe('remaining accounting', () => + { + it('decrements by the read size and reaches 0 at the end of the buffer', () => + { + writer.writeByte(1).writeShort(2).writeInt(3); + + const reader = new BinaryReader(writer.getBuffer()); + + expect(reader.remaining()).toBe(7); + + reader.readByte(); + expect(reader.remaining()).toBe(6); + + reader.readShort(); + expect(reader.remaining()).toBe(4); + + reader.readInt(); + expect(reader.remaining()).toBe(0); + }); + }); + + describe('float / double read', () => + { + // BinaryWriter has no write counterparts for float/double — build the + // buffer by hand via DataView and check the reader decodes correctly. + + it('readFloat decodes an IEEE-754 single-precision big-endian value', () => + { + const buf = new ArrayBuffer(4); + new DataView(buf).setFloat32(0, 3.5, false); + + const reader = new BinaryReader(buf); + + expect(reader.readFloat()).toBeCloseTo(3.5, 5); + expect(reader.remaining()).toBe(0); + }); + + it('readDouble decodes an IEEE-754 double-precision big-endian value', () => + { + const buf = new ArrayBuffer(8); + new DataView(buf).setFloat64(0, Math.PI, false); + + const reader = new BinaryReader(buf); + + expect(reader.readDouble()).toBeCloseTo(Math.PI, 12); + expect(reader.remaining()).toBe(0); + }); + }); + + describe('writer position getter/setter', () => + { + it('reports the position after writes', () => + { + writer.writeInt(0).writeShort(0); + + expect(writer.position).toBe(6); + }); + + it('position can be set explicitly (caller-managed reposition)', () => + { + writer.writeInt(0); + writer.position = 0; + + expect(writer.position).toBe(0); + }); + }); + + describe('typical packet round-trip (header + payload)', () => + { + it('encodes and decodes a header + mixed payload (short + int + string)', () => + { + const header = 1234; + const userId = 99999; + const username = 'simoleo'; + + writer + .writeShort(header) + .writeInt(userId) + .writeString(username); + + const reader = new BinaryReader(writer.getBuffer()); + + expect(reader.readShort()).toBe(header); + expect(reader.readInt()).toBe(userId); + + const nameLength = reader.readShort(); + const name = reader.readBytes(nameLength).toString(); + + expect(name).toBe(username); + expect(reader.remaining()).toBe(0); + }); + + it('concatenated buffers round-trip across independent writer instances', () => + { + const a = new BinaryWriter(); + const b = new BinaryWriter(); + + a.writeInt(11); + b.writeInt(22); + + const reader = new BinaryReader(concatBuffers(a.getBuffer(), b.getBuffer())); + + expect(reader.readInt()).toBe(11); + expect(reader.readInt()).toBe(22); + expect(reader.remaining()).toBe(0); + }); + }); +}); From a599e0cf8927540075b234f206cdcba31eabc9a3 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 18 May 2026 20:50:24 +0200 Subject: [PATCH 17/30] feat(session): snapshot getters for IgnoredUsersManager + GroupInformationManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the v2.1.0 React-friendly snapshot pattern (originally on SessionDataManager / RoomSessionManager) to two more session-state holders the React client reads frequently: - IgnoredUsersManager.getIgnoredUsersSnapshot(): ReadonlyArray - GroupInformationManager.getGroupBadgesSnapshot(): ReadonlyMap Both follow the same shape: lazy-frozen snapshot, cached until the underlying state mutates, then invalidated and a dispatched event lets the React client rebuild via useSyncExternalStore. Two new NitroEventType members carry the invalidation signal: - IGNORED_USERS_UPDATED — dispatched by IgnoredUsersManager whenever the list changes (initial load, add, remove, queue-truncate case 2). - GROUP_BADGES_UPDATED — dispatched by GroupInformationManager only when the incoming HabboGroupBadges payload contains at least one new or changed mapping (no-op refresh stays quiet). This lets the user-info popup, profile page, friend/guild filtering, and any other consumer share a single read through useSyncExternalStore instead of each subscribing to the underlying message events independently. API additions are interface-respecting and backwards-compatible — the existing `isIgnored(name)` / `getGroupBadge(groupId)` accessors stay untouched. --- .../nitro/session/IGroupInformationManager.ts | 9 ++++++ .../src/nitro/session/IIgnoredUsersManager.ts | 10 +++++++ packages/events/src/NitroEventType.ts | 2 ++ .../session/src/GroupInformationManager.ts | 30 ++++++++++++++++++- packages/session/src/IgnoredUsersManager.ts | 30 +++++++++++++++++-- 5 files changed, 78 insertions(+), 3 deletions(-) diff --git a/packages/api/src/nitro/session/IGroupInformationManager.ts b/packages/api/src/nitro/session/IGroupInformationManager.ts index 4304483..1fc8411 100644 --- a/packages/api/src/nitro/session/IGroupInformationManager.ts +++ b/packages/api/src/nitro/session/IGroupInformationManager.ts @@ -2,4 +2,13 @@ export interface IGroupInformationManager { init(): void; getGroupBadge(groupId: number): string; + + /** + * Returns the current `groupId -> badgeId` map as a frozen, + * referentially stable ReadonlyMap. The same reference is returned + * across reads until the underlying badges change; mutations + * dispatch `NitroEventType.GROUP_BADGES_UPDATED` to signal + * invalidation. + */ + getGroupBadgesSnapshot(): ReadonlyMap; } diff --git a/packages/api/src/nitro/session/IIgnoredUsersManager.ts b/packages/api/src/nitro/session/IIgnoredUsersManager.ts index aa065b3..f4b032e 100644 --- a/packages/api/src/nitro/session/IIgnoredUsersManager.ts +++ b/packages/api/src/nitro/session/IIgnoredUsersManager.ts @@ -6,4 +6,14 @@ export interface IIgnoredUsersManager ignoreUser(name: string): void; unignoreUser(name: string): void; isIgnored(name: string): boolean; + + /** + * Returns the current ignored-users list as a frozen, referentially + * stable array. The same reference is returned across reads until + * the list is mutated; mutations dispatch + * `NitroEventType.IGNORED_USERS_UPDATED` to signal invalidation. + * + * Pairs with `useSyncExternalStore` on the React client. + */ + getIgnoredUsersSnapshot(): ReadonlyArray; } diff --git a/packages/events/src/NitroEventType.ts b/packages/events/src/NitroEventType.ts index 6cfd367..5868bcc 100644 --- a/packages/events/src/NitroEventType.ts +++ b/packages/events/src/NitroEventType.ts @@ -19,4 +19,6 @@ export class NitroEventType public static readonly FURNITURE_DATA_LOADED = 'FURNITURE_DATA_LOADED'; public static readonly SESSION_DATA_UPDATED = 'SESSION_DATA_UPDATED'; public static readonly ROOM_SESSION_UPDATED = 'ROOM_SESSION_UPDATED'; + public static readonly IGNORED_USERS_UPDATED = 'IGNORED_USERS_UPDATED'; + public static readonly GROUP_BADGES_UPDATED = 'GROUP_BADGES_UPDATED'; } diff --git a/packages/session/src/GroupInformationManager.ts b/packages/session/src/GroupInformationManager.ts index fe93aeb..f323112 100644 --- a/packages/session/src/GroupInformationManager.ts +++ b/packages/session/src/GroupInformationManager.ts @@ -1,9 +1,11 @@ import { IGroupInformationManager } from '@nitrots/api'; import { GetCommunication, GetHabboGroupBadgesMessageComposer, HabboGroupBadgesMessageEvent, RoomReadyMessageEvent } from '@nitrots/communication'; +import { GetEventDispatcher, NitroEvent, NitroEventType } from '@nitrots/events'; export class GroupInformationManager implements IGroupInformationManager { private _groupBadges: Map = new Map(); + private _groupBadgesSnapshot: ReadonlyMap | null = null; public init(): void { @@ -20,11 +22,37 @@ export class GroupInformationManager implements IGroupInformationManager { const parser = event.getParser(); - for(const [groupId, badgeId] of parser.badges.entries()) this._groupBadges.set(groupId, badgeId); + let didChange = false; + + for(const [ groupId, badgeId ] of parser.badges.entries()) + { + if(this._groupBadges.get(groupId) === badgeId) continue; + + this._groupBadges.set(groupId, badgeId); + didChange = true; + } + + if(didChange) this.invalidateGroupBadgesSnapshot(); + } + + private invalidateGroupBadgesSnapshot(): void + { + this._groupBadgesSnapshot = null; + + GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.GROUP_BADGES_UPDATED)); } public getGroupBadge(groupId: number): string { return this._groupBadges.get(groupId) ?? ''; } + + public getGroupBadgesSnapshot(): ReadonlyMap + { + if(this._groupBadgesSnapshot) return this._groupBadgesSnapshot; + + this._groupBadgesSnapshot = new Map(this._groupBadges) as ReadonlyMap; + + return this._groupBadgesSnapshot; + } } diff --git a/packages/session/src/IgnoredUsersManager.ts b/packages/session/src/IgnoredUsersManager.ts index 77f1cd0..929bddc 100644 --- a/packages/session/src/IgnoredUsersManager.ts +++ b/packages/session/src/IgnoredUsersManager.ts @@ -1,9 +1,27 @@ import { IIgnoredUsersManager } from '@nitrots/api'; import { GetCommunication, GetIgnoredUsersComposer, IgnoreResultEvent, IgnoreUserComposer, IgnoreUserIdComposer, IgnoredUsersEvent, UnignoreUserComposer } from '@nitrots/communication'; +import { GetEventDispatcher, NitroEvent, NitroEventType } from '@nitrots/events'; export class IgnoredUsersManager implements IIgnoredUsersManager { private _ignoredUsers: string[] = []; + private _ignoredUsersSnapshot: ReadonlyArray | null = null; + + private invalidateIgnoredUsersSnapshot(): void + { + this._ignoredUsersSnapshot = null; + + GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.IGNORED_USERS_UPDATED)); + } + + public getIgnoredUsersSnapshot(): ReadonlyArray + { + if(this._ignoredUsersSnapshot) return this._ignoredUsersSnapshot; + + this._ignoredUsersSnapshot = Object.freeze([ ...this._ignoredUsers ]) as ReadonlyArray; + + return this._ignoredUsersSnapshot; + } public init(): void { @@ -25,6 +43,7 @@ export class IgnoredUsersManager implements IIgnoredUsersManager if(!parser) return; this._ignoredUsers = parser.ignoredUsers; + this.invalidateIgnoredUsersSnapshot(); } private onIgnoreResultEvent(event: IgnoreResultEvent): void @@ -47,6 +66,7 @@ export class IgnoredUsersManager implements IIgnoredUsersManager case 2: this.addUserToIgnoreList(name); this._ignoredUsers.shift(); + this.invalidateIgnoredUsersSnapshot(); return; case 3: this.removeUserFromIgnoreList(name); @@ -56,14 +76,20 @@ export class IgnoredUsersManager implements IIgnoredUsersManager private addUserToIgnoreList(name: string): void { - if(this._ignoredUsers.indexOf(name) < 0) this._ignoredUsers.push(name); + if(this._ignoredUsers.indexOf(name) >= 0) return; + + this._ignoredUsers.push(name); + this.invalidateIgnoredUsersSnapshot(); } private removeUserFromIgnoreList(name: string): void { const index = this._ignoredUsers.indexOf(name); - if(index >= 0) this._ignoredUsers.splice(index, 1); + if(index < 0) return; + + this._ignoredUsers.splice(index, 1); + this.invalidateIgnoredUsersSnapshot(); } public ignoreUserId(id: number): void From 761d8ffe1988ce971654aaf351569410724daf23 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 18 May 2026 20:52:33 +0200 Subject: [PATCH 18/30] feat(session): snapshot getter for UserDataManager room user list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the snapshot pattern to the room's user list. The React client currently has many widgets each calling `getUserDataByIndex(idx)` in a loop (chat, doorbell, room player list, infostand …) — every render walks the underlying Map and rebuilds an array. With `getRoomUserListSnapshot(): ReadonlyArray` consumers can memoize on the array reference and only rebuild when something actually changed. Invalidation fires on every state-changing path: - updateUserData (add/replace) - removeUserData (leave) - updateFigure / updateName / updateMotto / updateNickIcon / updateCustomization / updateBackground / updateAchievementScore / updatePetLevel / updatePetBreedingStatus The inner IRoomUserData objects keep their existing in-place mutation semantics (deep-clone would be too expensive for 30+ avatars on every single status push). Consumers should treat each entry as a snapshot-at-time-of-read and not retain references across an invalidation. New event: NitroEventType.ROOM_USER_LIST_UPDATED. Interface and event additions are backwards-compatible; no existing accessors changed. Also tidied: `updatePetLevel` now uses the explicit `if(!userData) return;` guard pattern matching the rest of the methods (was a one-line inline conditional). --- .../api/src/nitro/session/IUserDataManager.ts | 16 +++++++ packages/events/src/NitroEventType.ts | 1 + packages/session/src/UserDataManager.ts | 48 +++++++++++++++++-- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/packages/api/src/nitro/session/IUserDataManager.ts b/packages/api/src/nitro/session/IUserDataManager.ts index b854be0..9d204a7 100644 --- a/packages/api/src/nitro/session/IUserDataManager.ts +++ b/packages/api/src/nitro/session/IUserDataManager.ts @@ -23,4 +23,20 @@ export interface IUserDataManager updatePetLevel(roomIndex: number, level: number): void; updatePetBreedingStatus(roomIndex: number, canBreed: boolean, canHarvest: boolean, canRevive: boolean, hasBreedingPermission: boolean): void; requestPetInfo(id: number): void; + + /** + * Returns the current room's user list as a referentially-stable + * ReadonlyArray. The same array reference is returned across reads + * until any user is added, removed, or has a tracked field updated + * (figure / name / motto / nick icon / customization / background / + * achievement score / pet level / breeding status). Mutations + * dispatch `NitroEventType.ROOM_USER_LIST_UPDATED` to signal + * invalidation. + * + * The inner IRoomUserData objects keep the existing in-place + * mutation semantics — they are NOT deep-cloned. Treat them as + * snapshots-at-time-of-read; consumers should not retain individual + * entries across invalidations. + */ + getRoomUserListSnapshot(): ReadonlyArray; } diff --git a/packages/events/src/NitroEventType.ts b/packages/events/src/NitroEventType.ts index 5868bcc..414ecfa 100644 --- a/packages/events/src/NitroEventType.ts +++ b/packages/events/src/NitroEventType.ts @@ -21,4 +21,5 @@ export class NitroEventType public static readonly ROOM_SESSION_UPDATED = 'ROOM_SESSION_UPDATED'; public static readonly IGNORED_USERS_UPDATED = 'IGNORED_USERS_UPDATED'; public static readonly GROUP_BADGES_UPDATED = 'GROUP_BADGES_UPDATED'; + public static readonly ROOM_USER_LIST_UPDATED = 'ROOM_USER_LIST_UPDATED'; } diff --git a/packages/session/src/UserDataManager.ts b/packages/session/src/UserDataManager.ts index 374458c..3d70563 100644 --- a/packages/session/src/UserDataManager.ts +++ b/packages/session/src/UserDataManager.ts @@ -1,5 +1,6 @@ import { IRoomUserData, IUserDataManager } from '@nitrots/api'; import { GetCommunication, RequestPetInfoComposer, UserCurrentBadgesComposer } from '@nitrots/communication'; +import { GetEventDispatcher, NitroEvent, NitroEventType } from '@nitrots/events'; export class UserDataManager implements IUserDataManager { @@ -11,6 +12,23 @@ export class UserDataManager implements IUserDataManager private _userDataByType: Map> = new Map(); private _userDataByRoomIndex: Map = new Map(); private _userBadges: Map = new Map(); + private _roomUserListSnapshot: ReadonlyArray | null = null; + + private invalidateRoomUserListSnapshot(): void + { + this._roomUserListSnapshot = null; + + GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.ROOM_USER_LIST_UPDATED)); + } + + public getRoomUserListSnapshot(): ReadonlyArray + { + if(this._roomUserListSnapshot) return this._roomUserListSnapshot; + + this._roomUserListSnapshot = Object.freeze([ ...this._userDataByRoomIndex.values() ]) as ReadonlyArray; + + return this._roomUserListSnapshot; + } public getUserData(webID: number): IRoomUserData { @@ -84,6 +102,8 @@ export class UserDataManager implements IUserDataManager existingType.set(data.webID, data); this._userDataByRoomIndex.set(data.roomIndex, data); + + this.invalidateRoomUserListSnapshot(); } public removeUserData(roomIndex: number): void @@ -97,6 +117,8 @@ export class UserDataManager implements IUserDataManager const existingType = this._userDataByType.get(existing.type); if(existingType) existingType.delete(existing.webID); + + this.invalidateRoomUserListSnapshot(); } public getUserBadges(userId: number): string[] @@ -125,6 +147,8 @@ export class UserDataManager implements IUserDataManager userData.sex = sex; userData.hasSaddle = hasSaddle; userData.isRiding = isRiding; + + this.invalidateRoomUserListSnapshot(); } public updateName(roomIndex: number, name: string): void @@ -134,6 +158,8 @@ export class UserDataManager implements IUserDataManager if(!userData) return; userData.name = name; + + this.invalidateRoomUserListSnapshot(); } public updateMotto(roomIndex: number, custom: string): void @@ -143,6 +169,8 @@ export class UserDataManager implements IUserDataManager if(!userData) return; userData.custom = custom; + + this.invalidateRoomUserListSnapshot(); } public updateNickIcon(roomIndex: number, nickIcon: string): void @@ -152,6 +180,8 @@ export class UserDataManager implements IUserDataManager if(!userData) return; userData.nickIcon = nickIcon; + + this.invalidateRoomUserListSnapshot(); } public updateCustomization(roomIndex: number, nickIcon: string, prefixText: string, prefixColor: string, prefixIcon: string, prefixEffect: string, prefixFont: string, displayOrder: string): void @@ -167,9 +197,11 @@ export class UserDataManager implements IUserDataManager userData.prefixEffect = prefixEffect; userData.prefixFont = prefixFont; userData.displayOrder = displayOrder; + + this.invalidateRoomUserListSnapshot(); } - - public updateBackground(roomIndex: number, background: number, stand: number, overlay: number, cardBackground: number = 0): void + + public updateBackground(roomIndex: number, background: number, stand: number, overlay: number, cardBackground: number = 0): void { const userData = this.getUserDataByIndex(roomIndex); @@ -179,6 +211,8 @@ export class UserDataManager implements IUserDataManager userData.stand = stand; userData.overlay = overlay; userData.cardBackground = cardBackground; + + this.invalidateRoomUserListSnapshot(); } public updateAchievementScore(roomIndex: number, score: number): void @@ -188,13 +222,19 @@ export class UserDataManager implements IUserDataManager if(!userData) return; userData.activityPoints = score; + + this.invalidateRoomUserListSnapshot(); } public updatePetLevel(roomIndex: number, level: number): void { const userData = this.getUserDataByIndex(roomIndex); - if(userData) userData.petLevel = level; + if(!userData) return; + + userData.petLevel = level; + + this.invalidateRoomUserListSnapshot(); } public updatePetBreedingStatus(roomIndex: number, canBreed: boolean, canHarvest: boolean, canRevive: boolean, hasBreedingPermission: boolean): void @@ -207,6 +247,8 @@ export class UserDataManager implements IUserDataManager userData.canHarvest = canHarvest; userData.canRevive = canRevive; userData.hasBreedingPermission = hasBreedingPermission; + + this.invalidateRoomUserListSnapshot(); } public requestPetInfo(id: number): void From 892d16b393a7d7f62ff8ace5cb9dcc9b4f338ddc Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 18 May 2026 20:55:12 +0200 Subject: [PATCH 19/30] feat(sound): snapshot getter + volume-update event on SoundManager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the snapshot pattern to the three audio volume levels (system / furni / trax) so volume-slider widgets on the React client can subscribe to a single source of truth via useSyncExternalStore. API additions on ISoundManager: - systemVolume / furniVolume getters (parity with the existing traxVolume getter) - getVolumesSnapshot(): Readonly with the same lazy-frozen + invalidation-on-change semantics as the user/session snapshots - new ISoundVolumesSnapshot { system, furni, trax } interface New event: NitroEventType.SOUND_VOLUMES_UPDATED. Dispatched only when the incoming NitroSettingsEvent.SETTINGS_UPDATED actually changes one of the three volumes (a no-op refresh stays quiet). While in there, fixed a real bug: the previous implementation cached `volumeFurniUpdated` / `volumeTraxUpdated` BEFORE writing the new values, but read `castedEvent.volumeFurni` / `castedEvent.volumeTrax` in their pre-division form — comparing percent (e.g. 75) against the already-divided stored value (e.g. 0.75) — so the change check almost always reported "updated" for a real settings push and never reported "updated" if the percent matched the stored fraction by coincidence (only 0/100 are stable). Updated check is now consistent (compare fraction vs fraction) and also tracks systemVolume changes for the new snapshot invalidation. --- packages/api/src/nitro/sound/ISoundManager.ts | 14 +++++ .../src/nitro/sound/ISoundVolumesSnapshot.ts | 6 +++ packages/api/src/nitro/sound/index.ts | 1 + packages/events/src/NitroEventType.ts | 1 + packages/sound/src/SoundManager.ts | 52 ++++++++++++++++--- 5 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 packages/api/src/nitro/sound/ISoundVolumesSnapshot.ts diff --git a/packages/api/src/nitro/sound/ISoundManager.ts b/packages/api/src/nitro/sound/ISoundManager.ts index 87714b5..8db15ba 100644 --- a/packages/api/src/nitro/sound/ISoundManager.ts +++ b/packages/api/src/nitro/sound/ISoundManager.ts @@ -1,8 +1,22 @@ import { IMusicController } from './IMusicController'; +import { ISoundVolumesSnapshot } from './ISoundVolumesSnapshot'; export interface ISoundManager { init(): Promise; musicController: IMusicController; traxVolume: number; + systemVolume: number; + furniVolume: number; + + /** + * Returns a referentially-stable snapshot of the three volume + * levels (system / furni / trax). The same reference is returned + * across reads until a volume changes; mutations dispatch + * `NitroEventType.SOUND_VOLUMES_UPDATED` to signal invalidation. + * + * Pairs with `useSyncExternalStore` on the React client for + * volume-slider widgets. + */ + getVolumesSnapshot(): Readonly; } diff --git a/packages/api/src/nitro/sound/ISoundVolumesSnapshot.ts b/packages/api/src/nitro/sound/ISoundVolumesSnapshot.ts new file mode 100644 index 0000000..f61c1e1 --- /dev/null +++ b/packages/api/src/nitro/sound/ISoundVolumesSnapshot.ts @@ -0,0 +1,6 @@ +export interface ISoundVolumesSnapshot +{ + system: number; + furni: number; + trax: number; +} diff --git a/packages/api/src/nitro/sound/index.ts b/packages/api/src/nitro/sound/index.ts index be4bfe4..f1e1c21 100644 --- a/packages/api/src/nitro/sound/index.ts +++ b/packages/api/src/nitro/sound/index.ts @@ -2,3 +2,4 @@ export * from './IMusicController'; export * from './IPlaylistController'; export * from './ISongInfo'; export * from './ISoundManager'; +export * from './ISoundVolumesSnapshot'; diff --git a/packages/events/src/NitroEventType.ts b/packages/events/src/NitroEventType.ts index 414ecfa..b7a1ea0 100644 --- a/packages/events/src/NitroEventType.ts +++ b/packages/events/src/NitroEventType.ts @@ -22,4 +22,5 @@ export class NitroEventType public static readonly IGNORED_USERS_UPDATED = 'IGNORED_USERS_UPDATED'; public static readonly GROUP_BADGES_UPDATED = 'GROUP_BADGES_UPDATED'; public static readonly ROOM_USER_LIST_UPDATED = 'ROOM_USER_LIST_UPDATED'; + public static readonly SOUND_VOLUMES_UPDATED = 'SOUND_VOLUMES_UPDATED'; } diff --git a/packages/sound/src/SoundManager.ts b/packages/sound/src/SoundManager.ts index e736fd4..db73f2c 100644 --- a/packages/sound/src/SoundManager.ts +++ b/packages/sound/src/SoundManager.ts @@ -1,6 +1,6 @@ -import { IAdvancedMap, IMusicController, INitroEvent, ISoundManager } from '@nitrots/api'; +import { IAdvancedMap, IMusicController, INitroEvent, ISoundManager, ISoundVolumesSnapshot } from '@nitrots/api'; import { GetConfiguration } from '@nitrots/configuration'; -import { GetEventDispatcher, NitroSettingsEvent, NitroSoundEvent, RoomEngineEvent, RoomEngineObjectEvent, RoomEngineSamplePlaybackEvent } from '@nitrots/events'; +import { GetEventDispatcher, NitroEvent, NitroEventType, NitroSettingsEvent, NitroSoundEvent, RoomEngineEvent, RoomEngineObjectEvent, RoomEngineSamplePlaybackEvent } from '@nitrots/events'; import { AdvancedMap, NitroLogger } from '@nitrots/utils'; import { MusicController } from './music/MusicController'; @@ -9,6 +9,7 @@ export class SoundManager implements ISoundManager private _volumeSystem: number = 0.5; private _volumeFurni: number = 0.5; private _volumeTrax: number = 0.5; + private _volumesSnapshot: Readonly | null = null; private _internalSamples: IAdvancedMap = new AdvancedMap(); private _furniSamples: IAdvancedMap = new AdvancedMap(); @@ -81,17 +82,24 @@ export class SoundManager implements ISoundManager case NitroSettingsEvent.SETTINGS_UPDATED: { const castedEvent = (event as NitroSettingsEvent); - const volumeFurniUpdated = castedEvent.volumeFurni !== this._volumeFurni; - const volumeTraxUpdated = castedEvent.volumeTrax !== this._volumeTrax; + const nextSystem = (castedEvent.volumeSystem / 100); + const nextFurni = (castedEvent.volumeFurni / 100); + const nextTrax = (castedEvent.volumeTrax / 100); - this._volumeSystem = (castedEvent.volumeSystem / 100); - this._volumeFurni = (castedEvent.volumeFurni / 100); - this._volumeTrax = (castedEvent.volumeTrax / 100); + const volumeSystemUpdated = nextSystem !== this._volumeSystem; + const volumeFurniUpdated = nextFurni !== this._volumeFurni; + const volumeTraxUpdated = nextTrax !== this._volumeTrax; + + this._volumeSystem = nextSystem; + this._volumeFurni = nextFurni; + this._volumeTrax = nextTrax; if(volumeFurniUpdated) this.updateFurniSamplesVolume(this._volumeFurni); if(volumeTraxUpdated) this._musicController?.updateVolume(this._volumeTrax); + if(volumeSystemUpdated || volumeFurniUpdated || volumeTraxUpdated) this.invalidateVolumesSnapshot(); + return; } case NitroSoundEvent.PLAY_SOUND: { @@ -215,8 +223,38 @@ export class SoundManager implements ISoundManager return this._volumeTrax; } + public get systemVolume(): number + { + return this._volumeSystem; + } + + public get furniVolume(): number + { + return this._volumeFurni; + } + public get musicController(): IMusicController { return this._musicController; } + + private invalidateVolumesSnapshot(): void + { + this._volumesSnapshot = null; + + GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SOUND_VOLUMES_UPDATED)); + } + + public getVolumesSnapshot(): Readonly + { + if(this._volumesSnapshot) return this._volumesSnapshot; + + this._volumesSnapshot = Object.freeze({ + system: this._volumeSystem, + furni: this._volumeFurni, + trax: this._volumeTrax + }); + + return this._volumesSnapshot; + } } From d740f833eb1bfd214826b35eac310901a764f744 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 18 May 2026 20:57:28 +0200 Subject: [PATCH 20/30] refactor(parsers): flatten nested bytesAvailable guards on UserProfile + GetGuestRoomResult MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two parsers handle "one tier of optional trailing fields per emulator release" via nested if(wrapper.bytesAvailable) chains. The semantics were correct but each new block sat one extra indent deeper than the previous one, and adding tier N+1 quietly meant re-indenting everything above it. Replaced with a flat early-return chain that's diff-friendly when the next emulator version ships a new trailing block: if(!wrapper.bytesAvailable) return true; // block N reads if(!wrapper.bytesAvailable) return true; // block N+1 reads … Functionally equivalent — defaults still come from flush(), older servers still bail at whichever tier they don't ship. Each block is now also documented inline so the order/contract is obvious without cross-referencing Arcturus. In UserProfileParser, also straightened the cardBackgroundId tier: was an inline `(wrapper.bytesAvailable ? readInt() : 0)` mid-block; now a proper `if(!bytesAvailable) return true;` guard between blocks, matching the rest of the chain. --- .../GetGuestRoomResultMessageParser.ts | 19 +++++--- .../parser/user/data/UserProfileParser.ts | 46 +++++++++++-------- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/packages/communication/src/messages/parser/navigator/GetGuestRoomResultMessageParser.ts b/packages/communication/src/messages/parser/navigator/GetGuestRoomResultMessageParser.ts index 0595de2..91e5654 100644 --- a/packages/communication/src/messages/parser/navigator/GetGuestRoomResultMessageParser.ts +++ b/packages/communication/src/messages/parser/navigator/GetGuestRoomResultMessageParser.ts @@ -45,12 +45,19 @@ export class GetGuestRoomResultMessageParser implements IMessageParser this.data.canMute = wrapper.readBoolean(); this._chat = new RoomChatSettings(wrapper); - if(wrapper.bytesAvailable) - { - this._hotelTimeZoneId = wrapper.readString(); - this._hotelCurrentTimeMs = Number(wrapper.readString()) || 0; - if(wrapper.bytesAvailable) this._roomItemLimit = wrapper.readInt(); - } + // Optional trailing blocks, one tier per emulator release: + // block 1: hotel timezone id + current time ms (2 strings) + // block 2: room item limit (1 int) + // Flat early-return chain so an older server stops cleanly at + // whichever block it doesn't ship. Defaults from flush(). + if(!wrapper.bytesAvailable) return true; + + this._hotelTimeZoneId = wrapper.readString(); + this._hotelCurrentTimeMs = Number(wrapper.readString()) || 0; + + if(!wrapper.bytesAvailable) return true; + + this._roomItemLimit = wrapper.readInt(); return true; } diff --git a/packages/communication/src/messages/parser/user/data/UserProfileParser.ts b/packages/communication/src/messages/parser/user/data/UserProfileParser.ts index ef07255..4c24ab2 100644 --- a/packages/communication/src/messages/parser/user/data/UserProfileParser.ts +++ b/packages/communication/src/messages/parser/user/data/UserProfileParser.ts @@ -82,29 +82,35 @@ export class UserProfileParser implements IMessageParser this._secondsSinceLastVisit = wrapper.readInt(); this._openProfileWindow = wrapper.readBoolean(); - if(wrapper.bytesAvailable) - { - this._backgroundId = wrapper.readInt(); - this._standId = wrapper.readInt(); - this._overlayId = wrapper.readInt(); + // Optional trailing blocks, one tier per emulator release: + // block 1: background / stand / overlay (3 ints) + // block 2: card background (1 int) + // block 3: nick icon (1 string) + // block 4: prefix decoration set (6 strings) + // Each tier early-returns to keep the parser tolerant of older + // servers that don't ship the later blocks. Defaults set by flush(). + if(!wrapper.bytesAvailable) return true; - this._cardBackgroundId = (wrapper.bytesAvailable ? wrapper.readInt() : 0); + this._backgroundId = wrapper.readInt(); + this._standId = wrapper.readInt(); + this._overlayId = wrapper.readInt(); - if(wrapper.bytesAvailable) - { - this._nickIcon = wrapper.readString(); + if(!wrapper.bytesAvailable) return true; - if(wrapper.bytesAvailable) - { - this._prefixText = wrapper.readString(); - this._prefixColor = wrapper.readString(); - this._prefixIcon = wrapper.readString(); - this._prefixEffect = wrapper.readString(); - this._prefixFont = wrapper.readString(); - this._displayOrder = wrapper.readString(); - } - } - } + this._cardBackgroundId = wrapper.readInt(); + + if(!wrapper.bytesAvailable) return true; + + this._nickIcon = wrapper.readString(); + + if(!wrapper.bytesAvailable) return true; + + this._prefixText = wrapper.readString(); + this._prefixColor = wrapper.readString(); + this._prefixIcon = wrapper.readString(); + this._prefixEffect = wrapper.readString(); + this._prefixFont = wrapper.readString(); + this._displayOrder = wrapper.readString(); return true; } From 28c552f6f82a4d3d689e470a83f9ec28339c3877 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 18 May 2026 20:58:25 +0200 Subject: [PATCH 21/30] docs(CLAUDE.md): document new snapshot getters + flat bytesAvailable pattern Add the four 2026-05-18 snapshot additions (IgnoredUsers, GroupInformation, UserDataManager room list, SoundManager volumes) to the snapshot-getter table with their invalidation events, plus the 3-step checklist for adding new ones. Also document the flat bytesAvailable early-return pattern as the canonical shape for optional-trailing-field parsers (replaces the brittle nested if-chain). Note the SoundManager volume-diff bug fix landed alongside. --- CLAUDE.md | 73 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 16 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index cfca37b..1a727f1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -58,26 +58,35 @@ unsubscriber, no need to juggle callback identity. Implemented in Equivalent for packet streams. Implemented in `packages/communication/src/CommunicationManager.ts`. -### Snapshot getters on `SessionDataManager` + `RoomSessionManager` +### Snapshot getters (referentially stable, lazy-frozen, invalidated on mutation) -```ts -getUserDataSnapshot(): Readonly -getActiveRoomSessionSnapshot(): Readonly | null -``` +Pattern: `getXxxSnapshot()` returns a frozen value cached internally; +mutators call `invalidateXxxSnapshot()` which drops the cache AND +dispatches an invalidation event. The React side reads via +`useSyncExternalStore`. -Returns **referentially-stable** values: the same object reference is -returned across reads until invalidated. Invalidation happens via the -new event types `NitroEventType.SESSION_DATA_UPDATED` and -`NitroEventType.ROOM_SESSION_UPDATED`. +| Manager | Getter | Invalidation event | +|---|---|---| +| `SessionDataManager` | `getUserDataSnapshot(): Readonly` | `SESSION_DATA_UPDATED` | +| `RoomSessionManager` | `getActiveRoomSessionSnapshot(): Readonly \| null` | `ROOM_SESSION_UPDATED` | +| `IgnoredUsersManager` | `getIgnoredUsersSnapshot(): ReadonlyArray` | `IGNORED_USERS_UPDATED` | +| `GroupInformationManager` | `getGroupBadgesSnapshot(): ReadonlyMap` | `GROUP_BADGES_UPDATED` (only on real changes — no-op refresh stays quiet) | +| `UserDataManager` | `getRoomUserListSnapshot(): ReadonlyArray` | `ROOM_USER_LIST_UPDATED` (inner IRoomUserData kept mutable — don't deep-clone) | +| `SoundManager` | `getVolumesSnapshot(): Readonly` | `SOUND_VOLUMES_UPDATED` (only when a volume actually changes) | -When you mutate any field that the snapshot exposes, call the private -`invalidateUserDataSnapshot()` / `invalidateRoomSessionSnapshot()` — -that drops the cached snapshot and dispatches the invalidation event. -The React side rebuilds via `useSyncExternalStore`. +Snapshot interface contracts live under `packages/api/src/nitro/session/` +and `packages/api/src/nitro/sound/`. When adding a new snapshot, the +checklist is: +1. Define the `Ixxx Snapshot` interface in `packages/api/src/nitro/...` + and export it from the matching `index.ts`. +2. Add a `XXX_UPDATED` member to `packages/events/src/NitroEventType.ts`. +3. Add `getXxxSnapshot()` to the interface AND impl; cache + invalidate + on every mutation path (don't forget batch operations like queue + truncation — invalidate AFTER the full batch, not mid-way). -The interface contracts live in: -- `packages/api/src/nitro/session/IUserDataSnapshot.ts` -- `packages/api/src/nitro/session/IRoomSessionSnapshot.ts` +Adding snapshots here is the preferred way to unblock new React +widgets — prefer it over exposing raw event-listener APIs on the +client side. ## Recent renderer changes (`feat/react19-event-bus`) @@ -139,6 +148,38 @@ unchanged. `WiredUserVariablesRequestComposer`) annotate the return type `(): []` explicitly so `IMessageComposer<[]>` lines up. +### Optional-trailing-field parsers: flat early-return chain + +Parsers that read "one tier of optional trailing fields per emulator +release" (UserProfileParser, GetGuestRoomResultMessageParser, +RoomSettingsDataParser, ModeratorUserInfoData, UserSubscriptionParser +…) all use a flat chain: + +```ts +if(!wrapper.bytesAvailable) return true; +// block N reads +if(!wrapper.bytesAvailable) return true; +// block N+1 reads +… +``` + +Defaults come from `flush()`. When the next emulator release ships a +new trailing block, append `if(!wrapper.bytesAvailable) return true;` ++ the new reads. Do NOT nest with `if(wrapper.bytesAvailable) { … }` +— the nested form re-indents the whole chain on every new tier and +is the historical source of brittle reads. + +### Bug fix: `SoundManager` volume diff comparison + +`onEvent(SETTINGS_UPDATED)` cached `volumeFurniUpdated` / +`volumeTraxUpdated` by comparing `castedEvent.volumeFurni` (percent, +e.g. 75) against `this._volumeFurni` (fraction, e.g. 0.75) — so the +change check almost always reported "updated" for a real settings push +and only reported "unchanged" if the percent matched the fraction by +coincidence (0 / 100 only). Fixed: divide first, compare divided +values, then write. Also tracks `volumeSystemUpdated` for the new +`SOUND_VOLUMES_UPDATED` snapshot invalidation. + ### Bug fix: `PetBreedingMessageParser.bytesAvailable < 12` `bytesAvailable` is a boolean (the wrapper just answers "is there From ce561bd5b3cbf3a715b632e4df3945b949920aa9 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Tue, 19 May 2026 17:14:13 +0200 Subject: [PATCH 22/30] feat(utils): parallelize gamedata loader + structured fetch errors Three improvements on top of duckietm/Dev's new JSON5 + split-aware gamedata loader: 1. Parallel fetches inside loadGamedata: every file declared in a tier's manifest is now fetched with Promise.all. The merge step still walks the parts in declared order so override semantics (core -> custom -> seasonal, and within-tier declaration order) are preserved. Root-manifest files and per-tier manifest discovery also run concurrently. 2. tryFetchManifest distinguishes 404 from other failures. The previous tryFetchOrNull silently treated parse errors and 5xx as "manifest missing", so a malformed manifest.json5 made an entire tier vanish from the boot. Now only HTTP 404 returns null; every other failure propagates. 3. New ConfigJsonError class with phase ('fetch' | 'parse'), sourceUrl, and optional httpStatus. Exported isMissingResource() helper lets callers check for 404 without string-matching. Also: - mergeGamedata warns via NitroLogger when an array looks keyed by id/classname/name on >=80% of items but a few are missing the key (the previous behavior fell back to concat() and produced silent duplicates). - Removed the dead text === null/undefined branch in parseConfigJson (Response.text() never returns null). Verified: tsgo clean, 138/138 tests pass on the renderer, 207/207 tests pass on the client (no behavioral change to existing callers). --- packages/utils/src/GamedataLoader.ts | 109 +++++++++++++++++---------- packages/utils/src/JsonParser.ts | 52 ++++++++++--- yarn.lock | 63 +++++++++++++--- 3 files changed, 165 insertions(+), 59 deletions(-) diff --git a/packages/utils/src/GamedataLoader.ts b/packages/utils/src/GamedataLoader.ts index cf4dadc..cf2d794 100644 --- a/packages/utils/src/GamedataLoader.ts +++ b/packages/utils/src/GamedataLoader.ts @@ -1,4 +1,5 @@ -import { fetchConfigJson } from './JsonParser'; +import { ConfigJsonError, fetchConfigJson, isMissingResource } from './JsonParser'; +import { NitroLogger } from './NitroLogger'; export const DEFAULT_TIERS = [ 'core', 'custom', 'seasonal' ] as const; export type GamedataTier = typeof DEFAULT_TIERS[number] | string; @@ -28,51 +29,69 @@ const joinUrl = (base: string, path: string): string => return `${ cleanBase }${ cleanPath }`; }; -const tryFetchOrNull = async (url: string): Promise => +// Returns the parsed payload when the manifest exists, null on a clean 404. +// Re-throws on any other error (network failure, 5xx, parse error) so callers +// don't silently skip a tier because of a typo in manifest.json5. +const tryFetchManifest = async (url: string): Promise => { try { return await fetchConfigJson(url); } - catch + catch(err) { - return null; + if(isMissingResource(err)) return null; + throw err; } }; +// Try .json5 first, then .json — both treated as optional. Anything other +// than 404 on either bubbles up. +const tryFetchManifestPair = async (baseUrl: string, name: string): Promise => +{ + const json5 = await tryFetchManifest(joinUrl(baseUrl, `${ name }.json5`)); + if(json5 !== null) return json5; + + return await tryFetchManifest(joinUrl(baseUrl, `${ name }.json`)); +}; + const isPlainObject = (value: any): value is Record => !!value && typeof value === 'object' && !Array.isArray(value); -const arrayItemsLookKeyed = (arr: any[], idKeys: readonly string[]): string | null => +const arrayItemsLookKeyed = (arr: any[], idKeys: readonly string[], sourceLabel?: string): string | null => { if(!arr.length) return null; for(const key of idKeys) { - let allHave = true; + let have = 0; for(const item of arr) { - if(!isPlainObject(item) || item[key] === undefined || item[key] === null) - { - allHave = false; - break; - } + if(isPlainObject(item) && item[key] !== undefined && item[key] !== null) have++; } - if(allHave) return key; + if(have === arr.length) return key; + + // Heuristic: if most items are keyed but a few are not, the data is + // probably keyed and the outliers are bugs in the source data. + // Surface this so operators don't get silent duplicates after merge. + if(have > 0 && have / arr.length >= 0.8) + { + NitroLogger.warn(`mergeGamedata: ${ sourceLabel ? `${ sourceLabel }: ` : '' }array looks keyed by "${ key }" (${ have }/${ arr.length } items) but some entries are missing it — falling back to concat which may produce duplicates`); + } } return null; }; -export const mergeGamedata = (a: any, b: any, idKeys: readonly string[] = DEFAULT_ID_KEYS): any => +export const mergeGamedata = (a: any, b: any, idKeys: readonly string[] = DEFAULT_ID_KEYS, sourceLabel?: string): any => { if(b === undefined) return a; if(a === undefined) return b; if(Array.isArray(a) && Array.isArray(b)) { - const idKey = arrayItemsLookKeyed(a, idKeys) || arrayItemsLookKeyed(b, idKeys); + const idKey = arrayItemsLookKeyed(a, idKeys, sourceLabel) || arrayItemsLookKeyed(b, idKeys, sourceLabel); if(!idKey) return a.concat(b); @@ -92,7 +111,7 @@ export const mergeGamedata = (a: any, b: any, idKeys: readonly string[] = DEFAUL if(at !== undefined) { - out[at] = mergeGamedata(out[at], item, idKeys); + out[at] = mergeGamedata(out[at], item, idKeys, sourceLabel); } else { @@ -110,7 +129,7 @@ export const mergeGamedata = (a: any, b: any, idKeys: readonly string[] = DEFAUL for(const k of Object.keys(b)) { - out[k] = mergeGamedata(a[k], b[k], idKeys); + out[k] = mergeGamedata(a[k], b[k], idKeys, sourceLabel); } return out; @@ -130,6 +149,11 @@ interface RootManifest files?: string[]; } +// Load every file in `files` concurrently, return them in the original +// declared order so the merge step preserves override semantics. +const fetchFilesInOrder = async (baseUrl: string, files: readonly string[]): Promise => + Promise.all(files.map(file => fetchConfigJson(joinUrl(baseUrl, file)))); + export const loadGamedata = async (url: string, options: GamedataLoadOptions = {}): Promise => { if(!url) throw new Error('loadGamedata: empty URL'); @@ -140,42 +164,47 @@ export const loadGamedata = async (url: string, options: GamedataLoadOp } const idKeys = options.mergeArrayIdKeys ?? DEFAULT_ID_KEYS; - const rootManifest = await tryFetchOrNull(joinUrl(url, 'manifest.json5')) - ?? await tryFetchOrNull(joinUrl(url, 'manifest.json')); + const rootManifest = await tryFetchManifestPair(url, 'manifest'); const tiers = (rootManifest?.tiers && rootManifest.tiers.length) ? rootManifest.tiers : (options.tiers ?? DEFAULT_TIERS); + // Fetch root-level files in parallel with discovering each tier's + // manifest. Per-tier file batches stay sequenced relative to each other + // so override order (core → custom → seasonal) is preserved during + // merge, but fetches inside a tier batch run concurrently. + const [ rootParts, tierManifests ] = await Promise.all([ + rootManifest?.files?.length ? fetchFilesInOrder(url, rootManifest.files) : Promise.resolve([] as any[]), + Promise.all(tiers.map(async tier => + { + const tierUrl = joinUrl(url, `${ tier }/`); + const manifest = await tryFetchManifestPair(tierUrl, 'manifest'); + + return { tier, tierUrl, manifest }; + })) + ]); + let merged: any = undefined; - if(rootManifest?.files?.length) + for(const part of rootParts) { - for(const file of rootManifest.files) + merged = (merged === undefined) ? part : mergeGamedata(merged, part, idKeys, url); + } + + for(const { tier, tierUrl, manifest } of tierManifests) + { + if(!manifest?.files?.length) continue; + + const parts = await fetchFilesInOrder(tierUrl, manifest.files); + + for(const part of parts) { - const fileUrl = joinUrl(url, file); - const part = await fetchConfigJson(fileUrl); - merged = (merged === undefined) ? part : mergeGamedata(merged, part, idKeys); + merged = (merged === undefined) ? part : mergeGamedata(merged, part, idKeys, `${ url } (${ tier })`); } } - for(const tier of tiers) - { - const tierUrl = joinUrl(url, `${ tier }/`); - const tierManifest = await tryFetchOrNull(joinUrl(tierUrl, 'manifest.json5')) - ?? await tryFetchOrNull(joinUrl(tierUrl, 'manifest.json')); - - if(!tierManifest?.files?.length) continue; - - for(const file of tierManifest.files) - { - const fileUrl = joinUrl(tierUrl, file); - const part = await fetchConfigJson(fileUrl); - merged = (merged === undefined) ? part : mergeGamedata(merged, part, idKeys); - } - } - - if(merged === undefined) throw new Error(`loadGamedata: directory mode at "${ url }" produced no data — make sure at least one tier (core/custom/seasonal) has a manifest.json5 with a 'files' array`); + if(merged === undefined) throw new ConfigJsonError(`loadGamedata: directory mode at "${ url }" produced no data — make sure at least one tier (core/custom/seasonal) has a manifest.json5 with a 'files' array`, 'fetch', url); return merged as T; }; diff --git a/packages/utils/src/JsonParser.ts b/packages/utils/src/JsonParser.ts index c812d63..46f3631 100644 --- a/packages/utils/src/JsonParser.ts +++ b/packages/utils/src/JsonParser.ts @@ -5,6 +5,28 @@ declare const __NITRO_JSON_MODE__: 'legacy' | 'json5' | 'auto' | undefined; const JSON5_EXTENSION = /\.json5(?:[?#]|$)/i; const JSON5_MIME = /(?:application|text)\/(?:json5|x-json5)/i; +export type ConfigJsonErrorPhase = 'fetch' | 'parse'; + +export class ConfigJsonError extends Error +{ + public readonly phase: ConfigJsonErrorPhase; + public readonly sourceUrl: string; + public readonly httpStatus?: number; + + constructor(message: string, phase: ConfigJsonErrorPhase, sourceUrl: string, httpStatus?: number, cause?: unknown) + { + super(message); + this.name = 'ConfigJsonError'; + this.phase = phase; + this.sourceUrl = sourceUrl; + this.httpStatus = httpStatus; + if(cause !== undefined) (this as any).cause = cause; + } +} + +export const isMissingResource = (err: unknown): boolean => + err instanceof ConfigJsonError && err.phase === 'fetch' && err.httpStatus === 404; + const resolveJsonMode = (): 'legacy' | 'json5' | 'auto' => { try @@ -44,9 +66,7 @@ const formatStrictError = (sourceUrl: string, err: unknown): string => export const parseConfigJson = (text: string, sourceUrl: string = ''): T => { - if(text === null || text === undefined) throw new Error(`Empty response${ sourceUrl ? ` for "${ sourceUrl }"` : '' }`); - - const trimmed = text.length > 0 ? text : ''; + const trimmed = text ?? ''; const mode = resolveJsonMode(); if(mode === 'legacy') @@ -57,7 +77,7 @@ export const parseConfigJson = (text: string, sourceUrl: string = ''): } catch(err) { - throw new Error(formatStrictError(sourceUrl, err)); + throw new ConfigJsonError(formatStrictError(sourceUrl, err), 'parse', sourceUrl, undefined, err); } } @@ -69,7 +89,7 @@ export const parseConfigJson = (text: string, sourceUrl: string = ''): } catch(err) { - throw new Error(formatParseError(sourceUrl, err, err)); + throw new ConfigJsonError(formatParseError(sourceUrl, err, err), 'parse', sourceUrl, undefined, err); } } @@ -90,7 +110,7 @@ export const parseConfigJson = (text: string, sourceUrl: string = ''): } catch(json5Error) { - throw new Error(formatParseError(sourceUrl, strictError, json5Error)); + throw new ConfigJsonError(formatParseError(sourceUrl, strictError, json5Error), 'parse', sourceUrl, undefined, json5Error); } }; @@ -109,7 +129,7 @@ export const parseConfigJsonFromResponse = async (response: Response, s } catch(err) { - throw new Error(formatParseError(url, err, err)); + throw new ConfigJsonError(formatParseError(url, err, err), 'parse', url, undefined, err); } } @@ -118,9 +138,23 @@ export const parseConfigJsonFromResponse = async (response: Response, s export const fetchConfigJson = async (url: string, init?: RequestInit): Promise => { - const response = await fetch(url, init); + let response: Response | undefined; - if(!response || response.status !== 200) throw new Error(`Failed to fetch "${ url }" — server returned HTTP ${ response?.status ?? 'no response' }`); + try + { + response = await fetch(url, init); + } + catch(networkErr) + { + const message = (networkErr as Error)?.message || String(networkErr); + throw new ConfigJsonError(`Network error fetching "${ url }" — ${ message }`, 'fetch', url, undefined, networkErr); + } + + if(!response || response.status !== 200) + { + const status = response?.status; + throw new ConfigJsonError(`Failed to fetch "${ url }" — server returned HTTP ${ status ?? 'no response' }`, 'fetch', url, status); + } return parseConfigJsonFromResponse(response, url); }; diff --git a/yarn.lock b/yarn.lock index 288c306..aaeae8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -369,7 +369,7 @@ resolved "https://registry.yarnpkg.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== -"@thumbmarkjs/thumbmarkjs@^1.8.1": +"@thumbmarkjs/thumbmarkjs@^1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@thumbmarkjs/thumbmarkjs/-/thumbmarkjs-1.9.0.tgz#a6444ac1f924f061cfc1507a21dcaf83ee705cab" integrity sha512-6LooyYk8i5L2zEZgDMLE6m2sGDcIHHBiZfxdFp0A16Q4ZXafEmhHmt+zCqQEBMiQHi+08e/v5q77IY2KhvAJwg== @@ -527,6 +527,54 @@ "@typescript-eslint/types" "8.59.3" eslint-visitor-keys "^5.0.0" +"@typescript/native-preview-darwin-arm64@7.0.0-dev.20260519.1": + version "7.0.0-dev.20260519.1" + resolved "https://registry.yarnpkg.com/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20260519.1.tgz#deaa14299366a79917a6ec19fc7f240304c36afe" + integrity sha512-c9zdG6sGJf25Jpz04JgE23zhYeprqFypDGuqiX94yMTvR8IWXjq3R2oMnim66YLBDon/V1nCEy6cFixeSd/4fg== + +"@typescript/native-preview-darwin-x64@7.0.0-dev.20260519.1": + version "7.0.0-dev.20260519.1" + resolved "https://registry.yarnpkg.com/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20260519.1.tgz#a6348c134204afdcdfa8f9a0925091788fc994d7" + integrity sha512-N16V3wiM0tsNmSSA7nZrxqXXt5OCJxBwiCVn35rnA7fr4WzJw6rJmwf9heNNhZ6Gh4ne3+Pexajf5akzuHR75Q== + +"@typescript/native-preview-linux-arm64@7.0.0-dev.20260519.1": + version "7.0.0-dev.20260519.1" + resolved "https://registry.yarnpkg.com/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20260519.1.tgz#04f2f0b314e646a6921cc818db6bac92364bab24" + integrity sha512-ltf91vAwKdbu0SlRQbFgi1h5ZrLLrBn6a4qIeN2VILGbtYrCXnARHRznLBv81yUETQ7aVr/LSQcmsWo1ejCK0w== + +"@typescript/native-preview-linux-arm@7.0.0-dev.20260519.1": + version "7.0.0-dev.20260519.1" + resolved "https://registry.yarnpkg.com/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20260519.1.tgz#ad10403fb8ae6e2ed120cb9f81a029328a00e995" + integrity sha512-8v4BExeeuCTrhaSGfeIJqm3qQkTzlZix/Qd/FkPlWoz9f7d7COvXb3Z4qhbaVolL0MMnUvQ7m005Z4kYsZ645A== + +"@typescript/native-preview-linux-x64@7.0.0-dev.20260519.1": + version "7.0.0-dev.20260519.1" + resolved "https://registry.yarnpkg.com/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20260519.1.tgz#dde79208611da3855f995e595b9b790c622f726f" + integrity sha512-AVD0tczTtFCHNa4RQRVPvu8Hnw4P3hQ+OlUAjnz/lHowvc6o1pYB46elMqfDuaoWqIpv+EAkAPP4ipFCofJ5IA== + +"@typescript/native-preview-win32-arm64@7.0.0-dev.20260519.1": + version "7.0.0-dev.20260519.1" + resolved "https://registry.yarnpkg.com/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20260519.1.tgz#74d0cf98c75e2df20772ae2fa4c8139066e2cf7f" + integrity sha512-TM+qatljyejqjHevCta3WIH53i0oGC7K8SoJ6t+mf4cGMTpZTyd7NhC1ts7e6/aydZnG53Bsta2iQi1SMIlQEw== + +"@typescript/native-preview-win32-x64@7.0.0-dev.20260519.1": + version "7.0.0-dev.20260519.1" + resolved "https://registry.yarnpkg.com/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20260519.1.tgz#a8cfe9f730399cdc21a20c40a59aee15eef75a18" + integrity sha512-r9LEsoY7JC/82gXo8hlOmpQaUXcqmngCVOv+mUx1UeMt9f+1S6oNO0W48o75mlBqqC7jfcMHqw8YS4LfVxPRGw== + +"@typescript/native-preview@^7.0.0-dev.20260510.1": + version "7.0.0-dev.20260519.1" + resolved "https://registry.yarnpkg.com/@typescript/native-preview/-/native-preview-7.0.0-dev.20260519.1.tgz#0d865f5cc6d376d896fc031dfbb38ed0004153c6" + integrity sha512-VVER7vFUDdfm5k3jbH5765tVEJa7+0rTUkFeXyGYrXPxpw9BIjA0QDxdtdlRyaU8MCZV9IKZUo6doxeAQRAjPg== + optionalDependencies: + "@typescript/native-preview-darwin-arm64" "7.0.0-dev.20260519.1" + "@typescript/native-preview-darwin-x64" "7.0.0-dev.20260519.1" + "@typescript/native-preview-linux-arm" "7.0.0-dev.20260519.1" + "@typescript/native-preview-linux-arm64" "7.0.0-dev.20260519.1" + "@typescript/native-preview-linux-x64" "7.0.0-dev.20260519.1" + "@typescript/native-preview-win32-arm64" "7.0.0-dev.20260519.1" + "@typescript/native-preview-win32-x64" "7.0.0-dev.20260519.1" + "@vitest/coverage-v8@^4.0.18": version "4.1.6" resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-4.1.6.tgz#1eacee5def68dfcb08c3ed5355edbad2a4c869b3" @@ -1681,15 +1729,10 @@ typescript-eslint@^8.26.1: "@typescript-eslint/typescript-estree" "8.59.3" "@typescript-eslint/utils" "8.59.3" -typescript@~5.5.4: - version "5.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" - integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== - -typescript@~5.8.2: - version "5.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" - integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== +typescript@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.3.tgz#90251dc007916e972786cb94d74d15b185577d21" + integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== undici-types@~6.21.0: version "6.21.0" From 87e67d58df3400fece85ad155e79c7bbf54036b9 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Tue, 19 May 2026 18:37:57 +0200 Subject: [PATCH 23/30] feat(session): rank metadata in UserPermissions snapshot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the `UserPermissionsEvent` parser and `IUserDataSnapshot` with rank metadata mirrored from the Arcturus `permission_ranks` table: rankId, rankName, rankBadge, rankPrefix, rankPrefixColor. The new fields are appended to the wire payload AFTER the existing [clubLevel, securityLevel, isAmbassador] triple. The parser guards the trailing block with `if(!wrapper.bytesAvailable) return true;` so older emulators (that don't write the extension) keep working — the snapshot just exposes the defaults (rankId=0, empty strings) in that case. `SessionDataManager.onUserPermissionsEvent` stores the values; the snapshot builder includes them; existing `invalidateUserDataSnapshot()` semantics flow through unchanged, so a runtime promote/demote (via `HabboManager.setRank` → `UserPermissionsComposer`) auto-flips the React-side `useUserRank()` / `useHasRankLevel()` / `useIsRank()` consumers in the Nitro-V3 client. Companion changes: - Arcturus-Morningstar-Extended: `UserPermissionsComposer.composeInternal()` now appends the 5 extra fields (pending operator commit; see ../Arcturus-Morningstar-Extended/Emulator/src/main/java/ com/eu/habbo/messages/outgoing/users/UserPermissionsComposer.java). - Nitro-V3: `useSessionSnapshots.ts` exposes the new family (useUserRank / useHasRankLevel / useIsRank), replacing the SecurityLevel-based wrappers (useIsModerator etc.) that hardcoded the renderer enum names — those don't match the operator's `permission_ranks.rank_name` column. Verification: tsgo clean, vitest 138/138. --- .../src/nitro/session/IUserDataSnapshot.ts | 9 ++++ .../user/access/UserPermissionsParser.ts | 47 +++++++++++++++++++ packages/session/src/SessionDataManager.ts | 25 ++++++++-- 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/packages/api/src/nitro/session/IUserDataSnapshot.ts b/packages/api/src/nitro/session/IUserDataSnapshot.ts index 84c971f..9d7a24f 100644 --- a/packages/api/src/nitro/session/IUserDataSnapshot.ts +++ b/packages/api/src/nitro/session/IUserDataSnapshot.ts @@ -19,4 +19,13 @@ export interface IUserDataSnapshot isSystemShutdown: boolean; uiFlags: number; tags: ReadonlyArray; + // Rank metadata mirrored from `permission_ranks` (Arcturus emulator + // ≥ 4.2.10 ships these via `UserPermissionsComposer`). Older + // emulators leave them at the defaults (rankId=0, empty strings) + // because the renderer-side parser short-circuits on bytesAvailable. + rankId: number; + rankName: string; + rankBadge: string; + rankPrefix: string; + rankPrefixColor: string; } diff --git a/packages/communication/src/messages/parser/user/access/UserPermissionsParser.ts b/packages/communication/src/messages/parser/user/access/UserPermissionsParser.ts index 4f40f45..20d5fb6 100644 --- a/packages/communication/src/messages/parser/user/access/UserPermissionsParser.ts +++ b/packages/communication/src/messages/parser/user/access/UserPermissionsParser.ts @@ -5,12 +5,22 @@ export class UserPermissionsParser implements IMessageParser private _clubLevel: number; private _securityLevel: number; private _isAmbassador: boolean; + private _rankId: number; + private _rankName: string; + private _rankBadge: string; + private _rankPrefix: string; + private _rankPrefixColor: string; public flush(): boolean { this._clubLevel = 0; this._securityLevel = 0; this._isAmbassador = false; + this._rankId = 0; + this._rankName = ''; + this._rankBadge = ''; + this._rankPrefix = ''; + this._rankPrefixColor = ''; return true; } @@ -23,6 +33,18 @@ export class UserPermissionsParser implements IMessageParser this._securityLevel = wrapper.readInt(); this._isAmbassador = wrapper.readBoolean(); + // Optional trailing block (Arcturus-Morningstar-Extended ≥ 4.2.10): + // rank metadata appended in a backward-compatible way. Older + // emulators don't write these bytes so we keep the defaults + // from flush(). + if(!wrapper.bytesAvailable) return true; + + this._rankId = wrapper.readInt(); + this._rankName = wrapper.readString(); + this._rankBadge = wrapper.readString(); + this._rankPrefix = wrapper.readString(); + this._rankPrefixColor = wrapper.readString(); + return true; } @@ -40,4 +62,29 @@ export class UserPermissionsParser implements IMessageParser { return this._isAmbassador; } + + public get rankId(): number + { + return this._rankId; + } + + public get rankName(): string + { + return this._rankName; + } + + public get rankBadge(): string + { + return this._rankBadge; + } + + public get rankPrefix(): string + { + return this._rankPrefix; + } + + public get rankPrefixColor(): string + { + return this._rankPrefixColor; + } } diff --git a/packages/session/src/SessionDataManager.ts b/packages/session/src/SessionDataManager.ts index c2f15e9..c6c8b9b 100644 --- a/packages/session/src/SessionDataManager.ts +++ b/packages/session/src/SessionDataManager.ts @@ -32,6 +32,11 @@ export class SessionDataManager implements ISessionDataManager private _clubLevel: number = 0; private _securityLevel: number = 0; private _isAmbassador: boolean = false; + private _rankId: number = 0; + private _rankName: string = ''; + private _rankBadge: string = ''; + private _rankPrefix: string = ''; + private _rankPrefixColor: string = ''; private _noobnessLevel: number = -1; private _isEmailVerified: boolean = false; @@ -89,7 +94,12 @@ export class SessionDataManager implements ISessionDataManager isSystemOpen: this._systemOpen, isSystemShutdown: this._systemShutdown, uiFlags: this._uiFlags, - tags: Object.freeze([...this._tags]) as ReadonlyArray + tags: Object.freeze([...this._tags]) as ReadonlyArray, + rankId: this._rankId, + rankName: this._rankName, + rankBadge: this._rankBadge, + rankPrefix: this._rankPrefix, + rankPrefixColor: this._rankPrefixColor }); return this._userDataSnapshot; @@ -239,9 +249,16 @@ export class SessionDataManager implements ISessionDataManager { if(!event || !event.connection) return; - this._clubLevel = event.getParser().clubLevel; - this._securityLevel = event.getParser().securityLevel; - this._isAmbassador = event.getParser().isAmbassador; + const parser = event.getParser(); + + this._clubLevel = parser.clubLevel; + this._securityLevel = parser.securityLevel; + this._isAmbassador = parser.isAmbassador; + this._rankId = parser.rankId; + this._rankName = parser.rankName; + this._rankBadge = parser.rankBadge; + this._rankPrefix = parser.rankPrefix; + this._rankPrefixColor = parser.rankPrefixColor; this.invalidateUserDataSnapshot(); } From 159c5eb6e89cdc5223100b4f05fbb93f66386b5c Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Tue, 19 May 2026 18:59:35 +0200 Subject: [PATCH 24/30] feat(session): resolved permission map snapshot (USER_PERMISSIONS_UPDATED) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the wire pipeline for `Outgoing.UserPermissionsMapComposer = 10070` shipped by Arcturus-Morningstar-Extended ≥ 4.2.10. The composer sends the resolved `permission_definitions` map for the current user (filtered to ALLOWED / ROOM_OWNER entries) at login and after every `HabboManager.setRank` — so a runtime promote/demote re-derives every React-side permission gate. - NitroEventType.USER_PERMISSIONS_UPDATED — new invalidation event. - IncomingHeader.USER_PERMISSIONS_MAP = 10070. - UserPermissionsMapParser reads `int count + (string key, int value)*`. - UserPermissionsMapEvent + NitroMessages registration. - SessionDataManager._permissions Map + getPermissionsSnapshot() referentially-stable per the snapshot convention. New handler onUserPermissionsMapEvent copies the parser map into the manager (so the parser's mutable reference doesn't leak) and invalidates. - ISessionDataManager.getPermissionsSnapshot() — public contract. React-side consumers ship in the companion commit on feat/react19-modernization. The wire is backward-compatible: older emulators never send the packet, the snapshot stays empty Map, and all useHasPermission(key) gates return false (mod-only UI hidden by default = safe). Verification: tsgo clean, vitest 138/138. --- .../src/nitro/session/ISessionDataManager.ts | 7 +++ packages/communication/src/NitroMessages.ts | 3 +- .../src/messages/incoming/IncomingHeader.ts | 2 + .../user/access/UserPermissionsMapEvent.ts | 16 ++++++ .../messages/incoming/user/access/index.ts | 1 + .../user/access/UserPermissionsMapParser.ts | 54 +++++++++++++++++++ .../src/messages/parser/user/access/index.ts | 1 + packages/events/src/NitroEventType.ts | 1 + packages/session/src/SessionDataManager.ts | 47 +++++++++++++++- 9 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 packages/communication/src/messages/incoming/user/access/UserPermissionsMapEvent.ts create mode 100644 packages/communication/src/messages/parser/user/access/UserPermissionsMapParser.ts diff --git a/packages/api/src/nitro/session/ISessionDataManager.ts b/packages/api/src/nitro/session/ISessionDataManager.ts index 59b0d97..3c4ba6f 100644 --- a/packages/api/src/nitro/session/ISessionDataManager.ts +++ b/packages/api/src/nitro/session/ISessionDataManager.ts @@ -55,4 +55,11 @@ export interface ISessionDataManager uiFlags: number; tags: string[]; getUserDataSnapshot(): Readonly; + /** + * Referentially-stable view of the resolved permission map for + * the current user. Invalidated by `USER_PERMISSIONS_UPDATED`. + * Empty when the connected emulator doesn't ship the extended + * `UserPermissionsMapComposer` (Arcturus ≥ 4.2.10). + */ + getPermissionsSnapshot(): ReadonlyMap; } diff --git a/packages/communication/src/NitroMessages.ts b/packages/communication/src/NitroMessages.ts index 3caca87..368a5bf 100644 --- a/packages/communication/src/NitroMessages.ts +++ b/packages/communication/src/NitroMessages.ts @@ -1,5 +1,5 @@ import { IMessageConfiguration } from '@nitrots/api'; -import { AcceptFriendMessageComposer, AcceptFriendResultEvent, AcceptGameInviteMessageComposer, AcceptQuestMessageComposer, AccountSafetyLockStatusChangeMessageEvent, AchievementEvent, AchievementNotificationMessageEvent, AchievementResolutionCompletedMessageEvent, AchievementResolutionProgressMessageEvent, AchievementResolutionsMessageEvent, AchievementsEvent, AchievementsScoreEvent, ActivateQuestMessageComposer, ActivityPointNotificationMessageEvent, AddFavouriteRoomMessageComposer, AddJukeboxDiskComposer, AddSpamWallPostItMessageComposer, ApplySnapshotMessageComposer, ApplyTonerComposer, ApproveAllMembershipRequestsMessageComposer, ApproveNameMessageComposer, ApproveNameMessageEvent, AreaHideMessageEvent, AuthenticatedEvent, AuthenticationMessageComposer, AvailabilityStatusMessageEvent, AvailabilityTimeMessageEvent, AvailableCommandsEvent, AvatarEffectActivatedComposer, AvatarEffectActivatedEvent, AvatarEffectAddedEvent, AvatarEffectExpiredEvent, AvatarEffectSelectedComposer, AvatarEffectSelectedEvent, AvatarEffectsEvent, BadgePointLimitsEvent, BadgeReceivedEvent, BadgesEvent, BannedUsersFromRoomEvent, BonusRareInfoMessageEvent, BotAddedToInventoryEvent, BotCommandConfigurationEvent, BotErrorEvent, BotForceOpenContextMenuEvent, BotInventoryMessageEvent, BotPlaceComposer, BotReceivedMessageEvent, BotRemoveComposer, BotRemovedFromInventoryEvent, BotSkillListUpdateEvent, BotSkillSaveComposer, BreedPetsMessageComposer, BuildersClubFurniCountMessageEvent, BuildersClubPlaceRoomItemMessageComposer, BuildersClubPlaceWallItemMessageComposer, BuildersClubQueryFurniCountMessageComposer, BuildersClubSubscriptionStatusMessageEvent, BundleDiscountRulesetMessageEvent, BuyMarketplaceOfferMessageComposer, BuyMarketplaceTokensMessageComposer, CallForHelpDisabledNotifyMessageEvent, CallForHelpFromForumMessageMessageComposer, CallForHelpFromForumThreadMessageComposer, CallForHelpFromIMMessageComposer, CallForHelpFromPhotoMessageComposer, CallForHelpFromSelfieMessageComposer, CallForHelpMessageComposer, CallForHelpPendingCallsDeletedMessageEvent, CallForHelpPendingCallsMessageEvent, CallForHelpReplyMessageEvent, CallForHelpResultMessageEvent, CameraPublishStatusMessageEvent, CameraPurchaseOKMessageEvent, CameraSnapshotMessageEvent, CameraStorageUrlMessageEvent, CampaignCalendarDataMessageEvent, CampaignCalendarDoorOpenedMessageEvent, CancelEventMessageComposer, CancelMarketplaceOfferMessageComposer, CancelMysteryBoxWaitMessageEvent, CancelPetBreedingComposer, CancelQuestMessageComposer, CanCreateRoomEvent, CanCreateRoomEventEvent, CanCreateRoomMessageComposer, CatalogAdminCreateOfferComposer, CatalogAdminCreatePageComposer, CatalogAdminDeleteOfferComposer, CatalogAdminDeletePageComposer, CatalogAdminMoveOfferComposer, CatalogAdminMovePageComposer, CatalogAdminPublishComposer, CatalogAdminResultEvent, CatalogAdminSaveOfferComposer, CatalogAdminSavePageComposer, CatalogGroupsComposer, CatalogPageExpirationEvent, CatalogPageMessageEvent, CatalogPagesListEvent, CatalogPageWithEarliestExpiryMessageEvent, CatalogPublishedMessageEvent, CategoriesWithVisitorCountEvent, CfhChatlogEvent, CfhSanctionMessageEvent, CfhTopicsInitEvent, ChangeEmailComposer, ChangeEmailResultEvent, ChangeQueueMessageComposer, ChangeUserNameMessageComposer, ChangeUserNameResultMessageEvent, ChatReviewGuideDecidesOnOfferMessageComposer, ChatReviewGuideDetachedMessageComposer, ChatReviewGuideVoteMessageComposer, ChatReviewSessionCreateMessageComposer, ChatReviewSessionDetachedMessageEvent, ChatReviewSessionOfferedToGuideMessageEvent, ChatReviewSessionResultsMessageEvent, ChatReviewSessionStartedMessageEvent, ChatReviewSessionVotingStatusMessageEvent, CheckUserNameMessageComposer, CheckUserNameResultMessageEvent, ClickFurniMessageComposer, ClientHelloMessageComposer, ClientPingEvent, CloseIssueDefaultActionMessageComposer, CloseIssuesMessageComposer, ClubGiftInfoEvent, ClubGiftNotificationEvent, ClubGiftSelectedEvent, CommunityGoalEarnedPrizesMessageEvent, CommunityGoalHallOfFameMessageEvent, CommunityGoalProgressMessageEvent, CommunityGoalVoteMessageComposer, CommunityGoalVoteMessageEvent, CompetitionEntrySubmitResultEvent, CompetitionRoomsDataMessageEvent, CompetitionRoomsSearchMessageComposer, CompetitionStatusMessageEvent, CompetitionVotingInfoMessageEvent, CompleteDiffieHandshakeEvent, CompleteDiffieHandshakeMessageComposer, CompostPlantMessageComposer, ConcurrentUsersGoalProgressMessageEvent, ConfirmPetBreedingComposer, ConnectionErrorEvent, ControlYoutubeDisplayPlaybackMessageComposer, ConvertedRoomIdEvent, ConvertGlobalRoomIdMessageComposer, CraftableProductsEvent, CraftComposer, CraftingRecipeEvent, CraftingRecipesAvailableEvent, CraftingResultEvent, CraftSecretComposer, CreateFlatMessageComposer, CurrentTimingCodeMessageEvent, CustomUserNotificationMessageEvent, DeclineFriendMessageComposer, DefaultSanctionMessageComposer, DeleteBadgeMessageComposer, DeleteFavouriteRoomMessageComposer, DeleteItemMessageComposer, DeletePendingCallsForHelpMessageComposer, DeletePetMessageComposer, DesktopViewComposer, DesktopViewEvent, DiceValueMessageEvent, DirectSMSClubBuyAvailableMessageEvent, DisconnectMessageComposer, DisconnectReasonEvent, DoorbellMessageEvent, EditEventMessageComposer, ElementPointerMessageEvent, EmailStatusResultEvent, EpicPopupMessageEvent, ExtendedProfileChangedMessageEvent, ExtendRentOrBuyoutFurniMessageComposer, ExtendRentOrBuyoutStripItemMessageComposer, FavoriteMembershipUpdateMessageEvent, FavouriteChangedEvent, FavouritesEvent, FigureSetIdsMessageEvent, FigureUpdateEvent, FindFriendsProcessResultEvent, FindNewFriendsMessageComposer, FireworkChargeDataEvent, FlatAccessDeniedMessageEvent, FlatControllerAddedEvent, FlatControllerRemovedEvent, FlatControllersEvent, FlatCreatedEvent, FloodControlEvent, FloorHeightMapEvent, FollowFriendFailedEvent, FollowFriendMessageComposer, ForumDataMessageEvent, ForumsListMessageEvent, ForwardToACompetitionRoomMessageComposer, ForwardToARandomPromotedRoomMessageComposer, ForwardToASubmittableRoomMessageComposer, ForwardToRandomCompetitionRoomMessageComposer, ForwardToSomeRoomMessageComposer, FriendFurniConfirmLockMessageComposer, FriendListFragmentEvent, FriendListUpdateComposer, FriendListUpdateEvent, FriendNotificationEvent, FriendRequestQuestCompleteMessageComposer, FriendRequestsEvent, FurniRentOrBuyoutOfferMessageEvent, FurnitureAliasesComposer, FurnitureAliasesEvent, FurnitureColorWheelComposer, FurnitureDataEvent, FurnitureDiceActivateComposer, FurnitureDiceDeactivateComposer, FurnitureExchangeComposer, FurnitureFloorAddEvent, FurnitureFloorEvent, FurnitureFloorRemoveEvent, FurnitureFloorUpdateComposer, FurnitureFloorUpdateEvent, FurnitureGroupInfoComposer, FurnitureListAddOrUpdateEvent, FurnitureListComposer, FurnitureListEvent, FurnitureListInvalidateEvent, FurnitureListRemovedEvent, FurnitureMannequinSaveLookComposer, FurnitureMannequinSaveNameComposer, FurnitureMultiStateComposer, FurnitureOneWayDoorComposer, FurniturePickupAllComposer, FurniturePickupComposer, FurniturePlaceComposer, FurniturePlacePaintComposer, FurniturePostItPlaceComposer, FurniturePostItPlacedEvent, FurnitureRandomStateComposer, FurnitureStackHeightComposer, FurnitureStackHeightEvent, FurnitureWallAddEvent, FurnitureWallEvent, FurnitureWallMultiStateComposer, FurnitureWallRemoveEvent, FurnitureWallUpdateComposer, FurnitureWallUpdateEvent, FurniEditorBySpriteComposer, FurniEditorDeleteComposer, FurniEditorDetailComposer, FurniEditorDetailResultEvent, FurniEditorInteractionsComposer, FurniEditorInteractionsResultEvent, FurniEditorResultEvent, FurniEditorSearchComposer, FurniEditorSearchResultEvent, FurniEditorUpdateComposer, Game2AccountGameStatusMessageEvent, Game2CheckGameDirectoryStatusMessageComposer, Game2ExitGameMessageComposer, Game2GameChatMessageComposer, Game2GameDirectoryStatusMessageEvent, Game2GetAccountGameStatusMessageComposer, Game2GetWeeklyFriendsLeaderboardComposer, Game2GetWeeklyLeaderboardComposer, Game2InArenaQueueMessageEvent, Game2JoiningGameFailedMessageEvent, Game2LoadStageReadyMessageComposer, Game2PlayAgainMessageComposer, Game2RequestFullStatusUpdateMessageComposer, Game2StartingGameFailedMessageEvent, Game2StopCounterMessageEvent, Game2UserLeftGameMessageEvent, Game2WeeklyFriendsLeaderboardEvent, Game2WeeklyLeaderboardEvent, GameAchievementsMessageEvent, GameInviteMessageEvent, GameListMessageEvent, GameStatusMessageEvent, GameUnloadedMessageComposer, GenericErrorEvent, GetBadgePointLimitsComposer, GetBonusRareInfoMessageComposer, GetBotInventoryComposer, GetBundleDiscountRulesetComposer, GetCatalogIndexComposer, GetCatalogPageComposer, GetCatalogPageExpirationComposer, GetCatalogPageWithEarliestExpiryComposer, GetCategoriesWithUserCountMessageComposer, GetCfhChatlogMessageComposer, GetCfhStatusMessageComposer, GetClubGiftInfo, GetClubOffersMessageComposer, GetCommunityGoalEarnedPrizesMessageComposer, GetCommunityGoalHallOfFameMessageComposer, GetCommunityGoalProgressMessageComposer, GetConcurrentUsersGoalProgressMessageComposer, GetConcurrentUsersRewardMessageComposer, GetCraftableProductsComposer, GetCraftingRecipeComposer, GetCraftingRecipesAvailableComposer, GetCurrentTimingCodeMessageComposer, GetCustomRoomFilterMessageComposer, GetDailyQuestMessageComposer, GetDirectClubBuyAvailableComposer, GetEmailStatusComposer, GetExtendedProfileByNameMessageComposer, GetFaqCategoryMessageComposer, GetFaqTextMessageComposer, GetForumsListMessageComposer, GetForumStatsMessageComposer, GetFriendRequestsComposer, GetGameAchievementsMessageComposer, GetGameListMessageComposer, GetGameStatusMessageComposer, GetGiftMessageComposer, GetGiftWrappingConfigurationComposer, GetGuestRoomMessageComposer, GetGuestRoomResultEvent, GetGuideReportingStatusMessageComposer, GetHabboBasicMembershipExtendOfferComposer, GetHabboClubExtendOfferMessageComposer, GetHabboGroupBadgesMessageComposer, GetIgnoredUsersComposer, GetInterstitialMessageComposer, GetIsBadgeRequestFulfilledComposer, GetIsOfferGiftableComposer, GetIsUserPartOfCompetitionMessageComposer, GetItemDataComposer, GetJukeboxPlayListMessageComposer, GetLimitedOfferAppearingNextComposer, GetMarketplaceCanMakeOfferComposer, GetMarketplaceConfigurationMessageComposer, GetMarketplaceItemStatsComposer, GetMarketplaceOffersMessageComposer, GetMarketplaceOwnOffersMessageComposer, GetMessagesMessageComposer, GetModeratorRoomInfoMessageComposer, GetModeratorUserInfoMessageComposer, GetNextTargetedOfferComposer, GetNowPlayingMessageComposer, GetOccupiedTilesMessageComposer, GetOfficialRoomsMessageComposer, GetOfficialSongIdMessageComposer, GetPendingCallsForHelpMessageComposer, GetPetCommandsComposer, GetPopularRoomTagsMessageComposer, GetProductOfferComposer, GetPromoArticlesComposer, GetQuestsMessageComposer, GetQuizQuestionsComposer, GetRecyclerStatusMessageComposer, GetRentOrBuyoutOfferMessageComposer, GetResolutionAchievementsMessageComposer, GetRoomAdPurchaseInfoComposer, GetRoomChatlogMessageComposer, GetRoomEntryDataMessageComposer, GetRoomEntryTileMessageComposer, GetRoomVisitsMessageComposer, GetSeasonalCalendarDailyOfferComposer, GetSeasonalQuestsOnlyMessageComposer, GetSecondsUntilMessageComposer, GetSellablePetPalettesComposer, GetSongInfoMessageComposer, GetSoundMachinePlayListMessageComposer, GetSoundSettingsComposer, GetTalentTrackLevelMessageComposer, GetTargetedOfferComposer, GetThreadMessageComposer, GetThreadsMessageComposer, GetUnreadForumsCountMessageComposer, GetUserChatlogMessageComposer, GetUserEventCatsMessageComposer, GetUserFlatCatsMessageComposer, GetUserGameAchievementsMessageComposer, GetUserSongDisksMessageComposer, GetUserTagsComposer, GetWardrobeMessageComposer, GetWeeklyGameRewardComposer, GetWeeklyGameRewardWinnersComposer, GetYoutubeDisplayStatusMessageComposer, GiftReceiverNotFoundEvent, GiftWrappingConfigurationEvent, GotMysteryBoxPrizeMessageEvent, GoToFlatMessageComposer, GroupAdminGiveComposer, GroupAdminTakeComposer, GroupBadgePartsComposer, GroupBadgePartsEvent, GroupBuyComposer, GroupBuyDataComposer, GroupBuyDataEvent, GroupConfirmMemberRemoveEvent, GroupConfirmRemoveMemberComposer, GroupDeleteComposer, GroupDetailsChangedMessageEvent, GroupFavoriteComposer, GroupFurniContextMenuInfoMessageEvent, GroupInformationComposer, GroupInformationEvent, GroupJoinComposer, GroupMembersComposer, GroupMembersEvent, GroupMembershipAcceptComposer, GroupMembershipDeclineComposer, GroupMembershipRequestedMessageEvent, GroupPurchasedEvent, GroupRemoveMemberComposer, GroupSaveBadgeComposer, GroupSaveColorsComposer, GroupSaveInformationComposer, GroupSavePreferencesComposer, GroupSettingsComposer, GroupSettingsEvent, GroupUnfavoriteComposer, GuestRoomSearchResultEvent, GuideOnDutyStatusMessageEvent, GuideReportingStatusMessageEvent, GuideSessionAttachedMessageEvent, GuideSessionCreateMessageComposer, GuideSessionDetachedMessageEvent, GuideSessionEndedMessageEvent, GuideSessionErrorMessageEvent, GuideSessionFeedbackMessageComposer, GuideSessionGetRequesterRoomMessageComposer, GuideSessionGuideDecidesMessageComposer, GuideSessionInvitedToGuideRoomMessageEvent, GuideSessionInviteRequesterMessageComposer, GuideSessionIsTypingMessageComposer, GuideSessionMessageMessageComposer, GuideSessionMessageMessageEvent, GuideSessionOnDutyUpdateMessageComposer, GuideSessionPartnerIsTypingMessageEvent, GuideSessionReportMessageComposer, GuideSessionRequesterCancelsMessageComposer, GuideSessionRequesterRoomMessageEvent, GuideSessionResolvedMessageComposer, GuideSessionStartedMessageEvent, GuideTicketCreationResultMessageEvent, GuideTicketResolutionMessageEvent, GuildBaseSearchMessageComposer, GuildEditFailedMessageEvent, GuildForumThreadsEvent, GuildMemberMgmtFailedMessageEvent, GuildMembershipsMessageEvent, HabboBroadcastMessageEvent, HabboClubExtendOfferMessageEvent, HabboClubOffersMessageEvent, HabboGroupBadgesMessageEvent, HabboGroupDeactivatedMessageEvent, HabboGroupJoinFailedMessageEvent, HabboSearchComposer, HabboSearchResultEvent, HarvestPetMessageComposer, HotelClosedAndOpensEvent, HotelClosesAndWillOpenAtEvent, HotelMergeNameChangeEvent, HotelWillCloseInMinutesEvent, IdentityAccountsEvent, IgnoredUsersEvent, IgnoreResultEvent, IgnoreUserComposer, IgnoreUserIdComposer, InClientLinkEvent, IncomingHeader, InfoFeedEnableMessageEvent, InfoRetrieveMessageComposer, InitCameraMessageEvent, InitDiffieHandshakeEvent, InitDiffieHandshakeMessageComposer, InstantMessageErrorEvent, InterstitialMessageEvent, InterstitialShownMessageComposer, IsBadgeRequestFulfilledEvent, IsOfferGiftableMessageEvent, IssueCloseNotificationMessageEvent, IssueDeletedMessageEvent, IssueInfoMessageEvent, IssuePickFailedMessageEvent, IsUserPartOfCompetitionMessageEvent, ItemDataUpdateMessageEvent, JoinedQueueMessageEvent, JoiningQueueFailedMessageEvent, JoinQueueMessageComposer, JukeboxPlayListFullMessageEvent, JukeboxSongDisksMessageEvent, LagWarningReportMessageComposer, LeaveQueueMessageComposer, LeftQueueMessageEvent, LimitedEditionSoldOutEvent, LimitedOfferAppearingNextMessageEvent, LoadGameMessageEvent, LoadGameUrlEvent, LoveLockFurniFinishedEvent, LoveLockFurniFriendConfirmedEvent, LoveLockFurniStartEvent, MaintenanceStatusMessageEvent, MakeOfferMessageComposer, MarkCatalogNewAdditionsPageOpenedComposer, MarketplaceBuyOfferResultEvent, MarketplaceCancelOfferResultEvent, MarketplaceCanMakeOfferResult, MarketplaceConfigurationEvent, MarketplaceItemStatsEvent, MarketplaceMakeOfferResult, MarketPlaceOffersEvent, MarketplaceOwnOffersEvent, MessageErrorEvent, MessengerInitComposer, MessengerInitEvent, MiniMailNewMessageEvent, MiniMailUnreadCountEvent, ModAlertMessageComposer, ModBanMessageComposer, ModerateMessageMessageComposer, ModerateRoomMessageComposer, ModerateThreadMessageComposer, ModeratorActionMessageComposer, ModeratorActionResultMessageEvent, ModeratorCautionEvent, ModeratorInitMessageEvent, ModeratorMessageEvent, ModeratorRoomInfoEvent, ModeratorToolPreferencesEvent, ModeratorUserInfoEvent, ModKickMessageComposer, ModMessageMessageComposer, ModMuteMessageComposer, ModToolPreferencesComposer, ModToolSanctionComposer, ModTradingLockMessageComposer, MoodlightSettingsComposer, MoodlightSettingsSaveComposer, MoodlightTogggleStateComposer, MOTDNotificationEvent, MuteAllInRoomEvent, MyFavouriteRoomsSearchMessageComposer, MyFrequentRoomHistorySearchMessageComposer, MyFriendsRoomsSearchMessageComposer, MyGuildBasesSearchMessageComposer, MyRecommendedRoomsMessageComposer, MyRoomHistorySearchMessageComposer, MyRoomRightsSearchMessageComposer, MyRoomsSearchMessageComposer, MysteryBoxKeysEvent, MysteryBoxWaitingCanceledMessageComposer, NavigatorCategoryListModeComposer, NavigatorCollapsedEvent, NavigatorDeleteSavedSearchComposer, NavigatorHomeRoomEvent, NavigatorInitComposer, NavigatorLiftedEvent, NavigatorMetadataEvent, NavigatorOpenRoomCreatorEvent, NavigatorSearchCloseComposer, NavigatorSearchComposer, NavigatorSearchesEvent, NavigatorSearchEvent, NavigatorSearchOpenComposer, NavigatorSearchSaveComposer, NavigatorSettingsEvent, NavigatorSettingsSaveComposer, NewConsoleMessageEvent, NewFriendRequestEvent, NewUserExperienceGetGiftsComposer, NewUserExperienceGiftOfferMessageEvent, NewUserExperienceNotCompleteEvent, NewUserExperienceScriptProceedComposer, NoobnessLevelMessageEvent, NoOwnedRoomsAlertMessageEvent, NoSuchFlatEvent, NotEnoughBalanceMessageEvent, NotificationDialogMessageEvent, NowPlayingMessageEvent, ObjectsDataUpdateEvent, ObjectsRollingEvent, OfferRewardDeliveredMessageEvent, OfficialSongIdMessageEvent, OneWayDoorStatusMessageEvent, OpenCampaignCalendarDoorAsStaffComposer, OpenCampaignCalendarDoorComposer, OpenMessageComposer, OpenMysteryTrophyMessageComposer, OpenPetPackageMessageComposer, OpenPetPackageRequestedMessageEvent, OpenPetPackageResultMessageEvent, OpenPresentComposer, OpenQuestTrackerMessageComposer, OpenWelcomeGiftComposer, OutgoingHeader, PeerUsersClassificationMessageComposer, PerformanceLogMessageComposer, PerkAllowancesMessageEvent, PetAddedToInventoryEvent, PetBreedingResultEvent, PetExperienceEvent, PetFigureUpdateEvent, PetInfoEvent, PetInventoryEvent, PetLevelNotificationEvent, PetLevelUpdateMessageEvent, PetMountComposer, PetMoveComposer, PetPlaceComposer, PetPlacingErrorEvent, PetReceivedMessageEvent, PetRemoveComposer, PetRemovedFromInventory, PetRespectComposer, PetRespectNoficationEvent, PetScratchFailedMessageEvent, PetSelectedMessageComposer, PetStatusUpdateEvent, PetSupplementComposer, PetSupplementedNotificationEvent, PetTrainingPanelMessageEvent, PhoneCollectionStateMessageEvent, PhotoCompetitionMessageComposer, PickIssuesMessageComposer, PlayListMessageEvent, PlayListSongAddedMessageEvent, PollAnswerComposer, PollContentsEvent, PollErrorEvent, PollOfferEvent, PollRejectComposer, PollStartComposer, PongMessageComposer, PopularRoomsSearchMessageComposer, PopularRoomTagsResultEvent, PostMessageMessageComposer, PostMessageMessageEvent, PostQuizAnswersComposer, PostThreadMessageEvent, PresentOpenedMessageEvent, ProductOfferEvent, PromoArticlesMessageEvent, PublishPhotoMessageComposer, PurchaseBasicMembershipExtensionComposer, PurchaseErrorMessageEvent, PurchaseFromCatalogAsGiftComposer, PurchaseFromCatalogComposer, PurchaseNotAllowedMessageEvent, PurchaseOKMessageEvent, PurchasePhotoMessageComposer, PurchaseRoomAdMessageComposer, PurchaseTargetedOfferComposer, PurchaseVipMembershipExtensionComposer, QuestCancelledMessageEvent, QuestCompletedMessageEvent, QuestDailyMessageEvent, QuestionAnsweredEvent, QuestionEvent, QuestionFinishedEvent, QuestMessageEvent, QuestsMessageEvent, QuizDataMessageEvent, QuizResultsMessageEvent, RateFlatMessageComposer, RecycleItemsMessageComposer, RecyclerFinishedMessageEvent, RecyclerStatusMessageEvent, RedeemCommunityGoalPrizeMessageComposer, RedeemItemClothingComposer, RedeemMarketplaceOfferCreditsMessageComposer, RedeemVoucherMessageComposer, RejectQuestMessageComposer, RelationshipStatusInfoEvent, ReleaseIssuesMessageComposer, RemainingMuteEvent, RemoveAllRightsMessageComposer, RemoveFriendComposer, RemoveJukeboxDiskComposer, RemoveOwnRoomRightsRoomMessageComposer, RemovePetSaddleComposer, RemoveWallItemComposer, RenderRoomMessageComposer, RenderRoomThumbnailMessageComposer, RentableSpaceCancelRentMessageComposer, RentableSpaceRentFailedMessageEvent, RentableSpaceRentMessageComposer, RentableSpaceRentOkMessageEvent, RentableSpaceStatusMessageComposer, RentableSpaceStatusMessageEvent, RequestABadgeComposer, RequestAchievementsMessageComposer, RequestBadgesComposer, RequestBotCommandConfigurationComposer, RequestCameraConfigurationComposer, RequestFriendComposer, RequestFurniInventoryWhenNotInRoomComposer, RequestPetInfoComposer, RequestPetsComposer, RequestSpamWallPostItMessageEvent, ResetPhoneNumberStateMessageComposer, ResetResolutionAchievementMessageComposer, RespectReceivedEvent, RestoreClientMessageEvent, RoomAdErrorEvent, RoomAdEventTabAdClickedComposer, RoomAdEventTabViewedComposer, RoomAdPurchaseInfoEvent, RoomAdPurchaseInitiatedComposer, RoomAdSearchMessageComposer, RoomAmbassadorAlertComposer, RoomBannedUsersComposer, RoomBanUserComposer, RoomChatlogEvent, RoomChatSettingsEvent, RoomCompetitionInitMessageComposer, RoomDeleteComposer, RoomDimmerPresetsEvent, RoomDoorbellAcceptedEvent, RoomDoorbellAccessComposer, RoomEnterComposer, RoomEnterErrorEvent, RoomEnterEvent, RoomEntryInfoMessageEvent, RoomEntryTileMessageEvent, RoomEventCancelEvent, RoomEventEvent, RoomFilterSettingsMessageEvent, RoomForwardEvent, RoomGiveRightsComposer, RoomHeightMapEvent, RoomHeightMapUpdateEvent, RoomInviteErrorEvent, RoomInviteEvent, RoomKickUserComposer, RoomMessageNotificationMessageEvent, RoomMuteComposer, RoomMuteUserComposer, RoomNetworkOpenConnectionMessageComposer, RoomOccupiedTilesMessageEvent, RoomPaintEvent, RoomPollResultEvent, RoomReadyMessageEvent, RoomRightsClearEvent, RoomRightsEvent, RoomRightsOwnerEvent, RoomScoreEvent, RoomSettingsComposer, RoomSettingsDataEvent, RoomSettingsErrorEvent, RoomSettingsSavedEvent, RoomSettingsSaveErrorEvent, RoomSettingsUpdatedEvent, RoomsWhereMyFriendsAreSearchMessageComposer, RoomsWithHighestScoreSearchMessageComposer, RoomTakeRightsComposer, RoomTextSearchMessageComposer, RoomThumbnailUpdateResultEvent, RoomUnbanUserComposer, RoomUnitActionComposer, RoomUnitBackgroundComposer, RoomUnitChatComposer, RoomUnitChatEvent, RoomUnitChatShoutComposer, RoomUnitChatShoutEvent, RoomUnitChatStyleComposer, RoomUnitChatWhisperComposer, RoomUnitChatWhisperEvent, RoomUnitDanceComposer, RoomUnitDanceEvent, RoomUnitDropHandItemComposer, RoomUnitEffectEvent, RoomUnitEvent, RoomUnitExpressionEvent, RoomUnitGiveHandItemComposer, RoomUnitGiveHandItemPetComposer, RoomUnitHandItemEvent, RoomUnitHandItemReceivedEvent, RoomUnitIdleEvent, RoomUnitInfoEvent, RoomUnitLookComposer, RoomUnitNumberEvent, RoomUnitPostureComposer, RoomUnitRemoveEvent, RoomUnitSignComposer, RoomUnitStatusEvent, RoomUnitTypingEvent, RoomUnitTypingStartComposer, RoomUnitTypingStopComposer, RoomUnitWalkComposer, RoomUsersClassificationMessageComposer, RoomUsersWithRightsComposer, RoomVisitsEvent, RoomVisualizationSettingsEvent, SanctionStatusEvent, SaveRoomSettingsComposer, SaveWardrobeOutfitMessageComposer, ScrGetKickbackInfoMessageComposer, ScrSendKickbackInfoMessageEvent, SearchFaqsMessageComposer, SeasonalCalendarDailyOfferMessageEvent, SeasonalQuestsMessageEvent, SecondsUntilMessageEvent, SelectClubGiftComposer, SellablePetPalettesMessageEvent, SendMessageComposer, SendRoomInviteComposer, SetActivatedBadgesComposer, SetClothingChangeDataMessageComposer, SetItemDataMessageComposer, SetObjectDataMessageComposer, SetPhoneNumberVerificationStatusMessageComposer, SetRelationshipStatusComposer, SetRoomSessionTagsMessageComposer, SetTargetedOfferStateComposer, SetYoutubeDisplayPlaylistMessageComposer, ShopTargetedOfferViewedComposer, ShowEnforceRoomCategoryDialogEvent, ShowMysteryBoxWaitMessageEvent, SimpleAlertMessageEvent, SSOTicketMessageComposer, StartCampaignMessageComposer, StartRoomPollEvent, SubmitRoomToCompetitionMessageComposer, TalentLevelUpEvent, TalentTrackComposer, TalentTrackLevelMessageEvent, TalentTrackMessageEvent, TargetedOfferEvent, TargetedOfferNotFoundEvent, ThreadMessagesMessageEvent, ThumbnailStatusMessageEvent, TogglePetBreedingComposer, TogglePetRidingComposer, ToggleStaffPickMessageComposer, TradingAcceptComposer, TradingAcceptEvent, TradingCancelComposer, TradingCloseComposer, TradingCloseEvent, TradingCompletedEvent, TradingConfirmationComposer, TradingConfirmationEvent, TradingListAddItemComposer, TradingListAddItemsComposer, TradingListItemEvent, TradingListItemRemoveComposer, TradingNoSuchItemEvent, TradingNotOpenEvent, TradingOpenComposer, TradingOpenEvent, TradingOpenFailedEvent, TradingOtherNotAllowedEvent, TradingUnacceptComposer, TradingYouAreNotAllowedEvent, TraxSongInfoMessageEvent, TryPhoneNumberMessageComposer, TryPhoneNumberResultMessageEvent, TryVerificationCodeResultMessageEvent, UnblockGroupMemberMessageComposer, UnignoreUserComposer, UniqueIDMessageComposer, UnloadGameMessageEvent, UnreadForumsCountMessageEvent, UnseenItemsEvent, UnseenResetCategoryComposer, UnseenResetItemsComposer, UpdateActionMessageComposer, UpdateConditionMessageComposer, UpdateFloorPropertiesMessageComposer, UpdateForumReadMarkerMessageComposer, UpdateForumSettingsMessageComposer, UpdateFurniturePositionComposer, UpdateHomeRoomMessageComposer, UpdateMessageMessageEvent, UpdateRoomCategoryAndTradeSettingsComposer, UpdateRoomFilterMessageComposer, UpdateRoomThumbnailMessageComposer, UpdateThreadMessageComposer, UpdateThreadMessageEvent, UpdateTriggerMessageComposer, UsePetProductComposer, UserBannedMessageEvent, UserChatlogEvent, UserClassificationMessageEvent, UserCreditsEvent, UserCurrencyComposer, UserCurrencyEvent, UserCurrentBadgesComposer, UserCurrentBadgesEvent, UserEventCatsEvent, UserFigureComposer, UserFlatCatsEvent, UserGameAchievementsMessageEvent, UserInfoEvent, UserMottoComposer, UserNameChangeMessageEvent, UserPermissionsEvent, UserProfileComposer, UserProfileEvent, UserRelationshipsComposer, UserRespectComposer, UserSettingsCameraFollowComposer, UserSettingsEvent, UserSettingsOldChatComposer, UserSettingsRoomInvitesComposer, UserSettingsSoundComposer, UserSongDisksInventoryMessageEvent, UserSubscriptionComposer, UserSubscriptionEvent, UserTagsMessageEvent, UserUnbannedFromRoomEvent, UserWardrobePageEvent, VerifyCodeMessageComposer, VersionCheckMessageComposer, VisitUserComposer, VoteForRoomMessageComposer, VotePollCounterMessageComposer, VoucherRedeemErrorMessageEvent, VoucherRedeemOkMessageEvent, WardrobeMessageEvent, WeeklyCompetitiveFriendsLeaderboardEvent, WeeklyCompetitiveLeaderboardEvent, WeeklyGameRewardEvent, WeeklyGameRewardWinnersEvent, WelcomeGiftChangeEmailComposer, WelcomeGiftChangeEmailResultEvent, WelcomeGiftStatusEvent, WiredFurniActionEvent, WiredFurniConditionEvent, WiredFurniTriggerEvent, WiredOpenEvent, WiredRewardResultMessageEvent, WiredSaveSuccessEvent, WiredValidationErrorEvent, YouArePlayingGameEvent, YouAreSpectatorMessageEvent, YoutubeControlVideoMessageEvent, YoutubeDisplayPlaylistsEvent, YoutubeDisplayVideoMessageEvent, ClickUserMessageComposer } from './messages'; +import { AcceptFriendMessageComposer, AcceptFriendResultEvent, AcceptGameInviteMessageComposer, AcceptQuestMessageComposer, AccountSafetyLockStatusChangeMessageEvent, AchievementEvent, AchievementNotificationMessageEvent, AchievementResolutionCompletedMessageEvent, AchievementResolutionProgressMessageEvent, AchievementResolutionsMessageEvent, AchievementsEvent, AchievementsScoreEvent, ActivateQuestMessageComposer, ActivityPointNotificationMessageEvent, AddFavouriteRoomMessageComposer, AddJukeboxDiskComposer, AddSpamWallPostItMessageComposer, ApplySnapshotMessageComposer, ApplyTonerComposer, ApproveAllMembershipRequestsMessageComposer, ApproveNameMessageComposer, ApproveNameMessageEvent, AreaHideMessageEvent, AuthenticatedEvent, AuthenticationMessageComposer, AvailabilityStatusMessageEvent, AvailabilityTimeMessageEvent, AvailableCommandsEvent, AvatarEffectActivatedComposer, AvatarEffectActivatedEvent, AvatarEffectAddedEvent, AvatarEffectExpiredEvent, AvatarEffectSelectedComposer, AvatarEffectSelectedEvent, AvatarEffectsEvent, BadgePointLimitsEvent, BadgeReceivedEvent, BadgesEvent, BannedUsersFromRoomEvent, BonusRareInfoMessageEvent, BotAddedToInventoryEvent, BotCommandConfigurationEvent, BotErrorEvent, BotForceOpenContextMenuEvent, BotInventoryMessageEvent, BotPlaceComposer, BotReceivedMessageEvent, BotRemoveComposer, BotRemovedFromInventoryEvent, BotSkillListUpdateEvent, BotSkillSaveComposer, BreedPetsMessageComposer, BuildersClubFurniCountMessageEvent, BuildersClubPlaceRoomItemMessageComposer, BuildersClubPlaceWallItemMessageComposer, BuildersClubQueryFurniCountMessageComposer, BuildersClubSubscriptionStatusMessageEvent, BundleDiscountRulesetMessageEvent, BuyMarketplaceOfferMessageComposer, BuyMarketplaceTokensMessageComposer, CallForHelpDisabledNotifyMessageEvent, CallForHelpFromForumMessageMessageComposer, CallForHelpFromForumThreadMessageComposer, CallForHelpFromIMMessageComposer, CallForHelpFromPhotoMessageComposer, CallForHelpFromSelfieMessageComposer, CallForHelpMessageComposer, CallForHelpPendingCallsDeletedMessageEvent, CallForHelpPendingCallsMessageEvent, CallForHelpReplyMessageEvent, CallForHelpResultMessageEvent, CameraPublishStatusMessageEvent, CameraPurchaseOKMessageEvent, CameraSnapshotMessageEvent, CameraStorageUrlMessageEvent, CampaignCalendarDataMessageEvent, CampaignCalendarDoorOpenedMessageEvent, CancelEventMessageComposer, CancelMarketplaceOfferMessageComposer, CancelMysteryBoxWaitMessageEvent, CancelPetBreedingComposer, CancelQuestMessageComposer, CanCreateRoomEvent, CanCreateRoomEventEvent, CanCreateRoomMessageComposer, CatalogAdminCreateOfferComposer, CatalogAdminCreatePageComposer, CatalogAdminDeleteOfferComposer, CatalogAdminDeletePageComposer, CatalogAdminMoveOfferComposer, CatalogAdminMovePageComposer, CatalogAdminPublishComposer, CatalogAdminResultEvent, CatalogAdminSaveOfferComposer, CatalogAdminSavePageComposer, CatalogGroupsComposer, CatalogPageExpirationEvent, CatalogPageMessageEvent, CatalogPagesListEvent, CatalogPageWithEarliestExpiryMessageEvent, CatalogPublishedMessageEvent, CategoriesWithVisitorCountEvent, CfhChatlogEvent, CfhSanctionMessageEvent, CfhTopicsInitEvent, ChangeEmailComposer, ChangeEmailResultEvent, ChangeQueueMessageComposer, ChangeUserNameMessageComposer, ChangeUserNameResultMessageEvent, ChatReviewGuideDecidesOnOfferMessageComposer, ChatReviewGuideDetachedMessageComposer, ChatReviewGuideVoteMessageComposer, ChatReviewSessionCreateMessageComposer, ChatReviewSessionDetachedMessageEvent, ChatReviewSessionOfferedToGuideMessageEvent, ChatReviewSessionResultsMessageEvent, ChatReviewSessionStartedMessageEvent, ChatReviewSessionVotingStatusMessageEvent, CheckUserNameMessageComposer, CheckUserNameResultMessageEvent, ClickFurniMessageComposer, ClientHelloMessageComposer, ClientPingEvent, CloseIssueDefaultActionMessageComposer, CloseIssuesMessageComposer, ClubGiftInfoEvent, ClubGiftNotificationEvent, ClubGiftSelectedEvent, CommunityGoalEarnedPrizesMessageEvent, CommunityGoalHallOfFameMessageEvent, CommunityGoalProgressMessageEvent, CommunityGoalVoteMessageComposer, CommunityGoalVoteMessageEvent, CompetitionEntrySubmitResultEvent, CompetitionRoomsDataMessageEvent, CompetitionRoomsSearchMessageComposer, CompetitionStatusMessageEvent, CompetitionVotingInfoMessageEvent, CompleteDiffieHandshakeEvent, CompleteDiffieHandshakeMessageComposer, CompostPlantMessageComposer, ConcurrentUsersGoalProgressMessageEvent, ConfirmPetBreedingComposer, ConnectionErrorEvent, ControlYoutubeDisplayPlaybackMessageComposer, ConvertedRoomIdEvent, ConvertGlobalRoomIdMessageComposer, CraftableProductsEvent, CraftComposer, CraftingRecipeEvent, CraftingRecipesAvailableEvent, CraftingResultEvent, CraftSecretComposer, CreateFlatMessageComposer, CurrentTimingCodeMessageEvent, CustomUserNotificationMessageEvent, DeclineFriendMessageComposer, DefaultSanctionMessageComposer, DeleteBadgeMessageComposer, DeleteFavouriteRoomMessageComposer, DeleteItemMessageComposer, DeletePendingCallsForHelpMessageComposer, DeletePetMessageComposer, DesktopViewComposer, DesktopViewEvent, DiceValueMessageEvent, DirectSMSClubBuyAvailableMessageEvent, DisconnectMessageComposer, DisconnectReasonEvent, DoorbellMessageEvent, EditEventMessageComposer, ElementPointerMessageEvent, EmailStatusResultEvent, EpicPopupMessageEvent, ExtendedProfileChangedMessageEvent, ExtendRentOrBuyoutFurniMessageComposer, ExtendRentOrBuyoutStripItemMessageComposer, FavoriteMembershipUpdateMessageEvent, FavouriteChangedEvent, FavouritesEvent, FigureSetIdsMessageEvent, FigureUpdateEvent, FindFriendsProcessResultEvent, FindNewFriendsMessageComposer, FireworkChargeDataEvent, FlatAccessDeniedMessageEvent, FlatControllerAddedEvent, FlatControllerRemovedEvent, FlatControllersEvent, FlatCreatedEvent, FloodControlEvent, FloorHeightMapEvent, FollowFriendFailedEvent, FollowFriendMessageComposer, ForumDataMessageEvent, ForumsListMessageEvent, ForwardToACompetitionRoomMessageComposer, ForwardToARandomPromotedRoomMessageComposer, ForwardToASubmittableRoomMessageComposer, ForwardToRandomCompetitionRoomMessageComposer, ForwardToSomeRoomMessageComposer, FriendFurniConfirmLockMessageComposer, FriendListFragmentEvent, FriendListUpdateComposer, FriendListUpdateEvent, FriendNotificationEvent, FriendRequestQuestCompleteMessageComposer, FriendRequestsEvent, FurniRentOrBuyoutOfferMessageEvent, FurnitureAliasesComposer, FurnitureAliasesEvent, FurnitureColorWheelComposer, FurnitureDataEvent, FurnitureDiceActivateComposer, FurnitureDiceDeactivateComposer, FurnitureExchangeComposer, FurnitureFloorAddEvent, FurnitureFloorEvent, FurnitureFloorRemoveEvent, FurnitureFloorUpdateComposer, FurnitureFloorUpdateEvent, FurnitureGroupInfoComposer, FurnitureListAddOrUpdateEvent, FurnitureListComposer, FurnitureListEvent, FurnitureListInvalidateEvent, FurnitureListRemovedEvent, FurnitureMannequinSaveLookComposer, FurnitureMannequinSaveNameComposer, FurnitureMultiStateComposer, FurnitureOneWayDoorComposer, FurniturePickupAllComposer, FurniturePickupComposer, FurniturePlaceComposer, FurniturePlacePaintComposer, FurniturePostItPlaceComposer, FurniturePostItPlacedEvent, FurnitureRandomStateComposer, FurnitureStackHeightComposer, FurnitureStackHeightEvent, FurnitureWallAddEvent, FurnitureWallEvent, FurnitureWallMultiStateComposer, FurnitureWallRemoveEvent, FurnitureWallUpdateComposer, FurnitureWallUpdateEvent, FurniEditorBySpriteComposer, FurniEditorDeleteComposer, FurniEditorDetailComposer, FurniEditorDetailResultEvent, FurniEditorInteractionsComposer, FurniEditorInteractionsResultEvent, FurniEditorResultEvent, FurniEditorSearchComposer, FurniEditorSearchResultEvent, FurniEditorUpdateComposer, Game2AccountGameStatusMessageEvent, Game2CheckGameDirectoryStatusMessageComposer, Game2ExitGameMessageComposer, Game2GameChatMessageComposer, Game2GameDirectoryStatusMessageEvent, Game2GetAccountGameStatusMessageComposer, Game2GetWeeklyFriendsLeaderboardComposer, Game2GetWeeklyLeaderboardComposer, Game2InArenaQueueMessageEvent, Game2JoiningGameFailedMessageEvent, Game2LoadStageReadyMessageComposer, Game2PlayAgainMessageComposer, Game2RequestFullStatusUpdateMessageComposer, Game2StartingGameFailedMessageEvent, Game2StopCounterMessageEvent, Game2UserLeftGameMessageEvent, Game2WeeklyFriendsLeaderboardEvent, Game2WeeklyLeaderboardEvent, GameAchievementsMessageEvent, GameInviteMessageEvent, GameListMessageEvent, GameStatusMessageEvent, GameUnloadedMessageComposer, GenericErrorEvent, GetBadgePointLimitsComposer, GetBonusRareInfoMessageComposer, GetBotInventoryComposer, GetBundleDiscountRulesetComposer, GetCatalogIndexComposer, GetCatalogPageComposer, GetCatalogPageExpirationComposer, GetCatalogPageWithEarliestExpiryComposer, GetCategoriesWithUserCountMessageComposer, GetCfhChatlogMessageComposer, GetCfhStatusMessageComposer, GetClubGiftInfo, GetClubOffersMessageComposer, GetCommunityGoalEarnedPrizesMessageComposer, GetCommunityGoalHallOfFameMessageComposer, GetCommunityGoalProgressMessageComposer, GetConcurrentUsersGoalProgressMessageComposer, GetConcurrentUsersRewardMessageComposer, GetCraftableProductsComposer, GetCraftingRecipeComposer, GetCraftingRecipesAvailableComposer, GetCurrentTimingCodeMessageComposer, GetCustomRoomFilterMessageComposer, GetDailyQuestMessageComposer, GetDirectClubBuyAvailableComposer, GetEmailStatusComposer, GetExtendedProfileByNameMessageComposer, GetFaqCategoryMessageComposer, GetFaqTextMessageComposer, GetForumsListMessageComposer, GetForumStatsMessageComposer, GetFriendRequestsComposer, GetGameAchievementsMessageComposer, GetGameListMessageComposer, GetGameStatusMessageComposer, GetGiftMessageComposer, GetGiftWrappingConfigurationComposer, GetGuestRoomMessageComposer, GetGuestRoomResultEvent, GetGuideReportingStatusMessageComposer, GetHabboBasicMembershipExtendOfferComposer, GetHabboClubExtendOfferMessageComposer, GetHabboGroupBadgesMessageComposer, GetIgnoredUsersComposer, GetInterstitialMessageComposer, GetIsBadgeRequestFulfilledComposer, GetIsOfferGiftableComposer, GetIsUserPartOfCompetitionMessageComposer, GetItemDataComposer, GetJukeboxPlayListMessageComposer, GetLimitedOfferAppearingNextComposer, GetMarketplaceCanMakeOfferComposer, GetMarketplaceConfigurationMessageComposer, GetMarketplaceItemStatsComposer, GetMarketplaceOffersMessageComposer, GetMarketplaceOwnOffersMessageComposer, GetMessagesMessageComposer, GetModeratorRoomInfoMessageComposer, GetModeratorUserInfoMessageComposer, GetNextTargetedOfferComposer, GetNowPlayingMessageComposer, GetOccupiedTilesMessageComposer, GetOfficialRoomsMessageComposer, GetOfficialSongIdMessageComposer, GetPendingCallsForHelpMessageComposer, GetPetCommandsComposer, GetPopularRoomTagsMessageComposer, GetProductOfferComposer, GetPromoArticlesComposer, GetQuestsMessageComposer, GetQuizQuestionsComposer, GetRecyclerStatusMessageComposer, GetRentOrBuyoutOfferMessageComposer, GetResolutionAchievementsMessageComposer, GetRoomAdPurchaseInfoComposer, GetRoomChatlogMessageComposer, GetRoomEntryDataMessageComposer, GetRoomEntryTileMessageComposer, GetRoomVisitsMessageComposer, GetSeasonalCalendarDailyOfferComposer, GetSeasonalQuestsOnlyMessageComposer, GetSecondsUntilMessageComposer, GetSellablePetPalettesComposer, GetSongInfoMessageComposer, GetSoundMachinePlayListMessageComposer, GetSoundSettingsComposer, GetTalentTrackLevelMessageComposer, GetTargetedOfferComposer, GetThreadMessageComposer, GetThreadsMessageComposer, GetUnreadForumsCountMessageComposer, GetUserChatlogMessageComposer, GetUserEventCatsMessageComposer, GetUserFlatCatsMessageComposer, GetUserGameAchievementsMessageComposer, GetUserSongDisksMessageComposer, GetUserTagsComposer, GetWardrobeMessageComposer, GetWeeklyGameRewardComposer, GetWeeklyGameRewardWinnersComposer, GetYoutubeDisplayStatusMessageComposer, GiftReceiverNotFoundEvent, GiftWrappingConfigurationEvent, GotMysteryBoxPrizeMessageEvent, GoToFlatMessageComposer, GroupAdminGiveComposer, GroupAdminTakeComposer, GroupBadgePartsComposer, GroupBadgePartsEvent, GroupBuyComposer, GroupBuyDataComposer, GroupBuyDataEvent, GroupConfirmMemberRemoveEvent, GroupConfirmRemoveMemberComposer, GroupDeleteComposer, GroupDetailsChangedMessageEvent, GroupFavoriteComposer, GroupFurniContextMenuInfoMessageEvent, GroupInformationComposer, GroupInformationEvent, GroupJoinComposer, GroupMembersComposer, GroupMembersEvent, GroupMembershipAcceptComposer, GroupMembershipDeclineComposer, GroupMembershipRequestedMessageEvent, GroupPurchasedEvent, GroupRemoveMemberComposer, GroupSaveBadgeComposer, GroupSaveColorsComposer, GroupSaveInformationComposer, GroupSavePreferencesComposer, GroupSettingsComposer, GroupSettingsEvent, GroupUnfavoriteComposer, GuestRoomSearchResultEvent, GuideOnDutyStatusMessageEvent, GuideReportingStatusMessageEvent, GuideSessionAttachedMessageEvent, GuideSessionCreateMessageComposer, GuideSessionDetachedMessageEvent, GuideSessionEndedMessageEvent, GuideSessionErrorMessageEvent, GuideSessionFeedbackMessageComposer, GuideSessionGetRequesterRoomMessageComposer, GuideSessionGuideDecidesMessageComposer, GuideSessionInvitedToGuideRoomMessageEvent, GuideSessionInviteRequesterMessageComposer, GuideSessionIsTypingMessageComposer, GuideSessionMessageMessageComposer, GuideSessionMessageMessageEvent, GuideSessionOnDutyUpdateMessageComposer, GuideSessionPartnerIsTypingMessageEvent, GuideSessionReportMessageComposer, GuideSessionRequesterCancelsMessageComposer, GuideSessionRequesterRoomMessageEvent, GuideSessionResolvedMessageComposer, GuideSessionStartedMessageEvent, GuideTicketCreationResultMessageEvent, GuideTicketResolutionMessageEvent, GuildBaseSearchMessageComposer, GuildEditFailedMessageEvent, GuildForumThreadsEvent, GuildMemberMgmtFailedMessageEvent, GuildMembershipsMessageEvent, HabboBroadcastMessageEvent, HabboClubExtendOfferMessageEvent, HabboClubOffersMessageEvent, HabboGroupBadgesMessageEvent, HabboGroupDeactivatedMessageEvent, HabboGroupJoinFailedMessageEvent, HabboSearchComposer, HabboSearchResultEvent, HarvestPetMessageComposer, HotelClosedAndOpensEvent, HotelClosesAndWillOpenAtEvent, HotelMergeNameChangeEvent, HotelWillCloseInMinutesEvent, IdentityAccountsEvent, IgnoredUsersEvent, IgnoreResultEvent, IgnoreUserComposer, IgnoreUserIdComposer, InClientLinkEvent, IncomingHeader, InfoFeedEnableMessageEvent, InfoRetrieveMessageComposer, InitCameraMessageEvent, InitDiffieHandshakeEvent, InitDiffieHandshakeMessageComposer, InstantMessageErrorEvent, InterstitialMessageEvent, InterstitialShownMessageComposer, IsBadgeRequestFulfilledEvent, IsOfferGiftableMessageEvent, IssueCloseNotificationMessageEvent, IssueDeletedMessageEvent, IssueInfoMessageEvent, IssuePickFailedMessageEvent, IsUserPartOfCompetitionMessageEvent, ItemDataUpdateMessageEvent, JoinedQueueMessageEvent, JoiningQueueFailedMessageEvent, JoinQueueMessageComposer, JukeboxPlayListFullMessageEvent, JukeboxSongDisksMessageEvent, LagWarningReportMessageComposer, LeaveQueueMessageComposer, LeftQueueMessageEvent, LimitedEditionSoldOutEvent, LimitedOfferAppearingNextMessageEvent, LoadGameMessageEvent, LoadGameUrlEvent, LoveLockFurniFinishedEvent, LoveLockFurniFriendConfirmedEvent, LoveLockFurniStartEvent, MaintenanceStatusMessageEvent, MakeOfferMessageComposer, MarkCatalogNewAdditionsPageOpenedComposer, MarketplaceBuyOfferResultEvent, MarketplaceCancelOfferResultEvent, MarketplaceCanMakeOfferResult, MarketplaceConfigurationEvent, MarketplaceItemStatsEvent, MarketplaceMakeOfferResult, MarketPlaceOffersEvent, MarketplaceOwnOffersEvent, MessageErrorEvent, MessengerInitComposer, MessengerInitEvent, MiniMailNewMessageEvent, MiniMailUnreadCountEvent, ModAlertMessageComposer, ModBanMessageComposer, ModerateMessageMessageComposer, ModerateRoomMessageComposer, ModerateThreadMessageComposer, ModeratorActionMessageComposer, ModeratorActionResultMessageEvent, ModeratorCautionEvent, ModeratorInitMessageEvent, ModeratorMessageEvent, ModeratorRoomInfoEvent, ModeratorToolPreferencesEvent, ModeratorUserInfoEvent, ModKickMessageComposer, ModMessageMessageComposer, ModMuteMessageComposer, ModToolPreferencesComposer, ModToolSanctionComposer, ModTradingLockMessageComposer, MoodlightSettingsComposer, MoodlightSettingsSaveComposer, MoodlightTogggleStateComposer, MOTDNotificationEvent, MuteAllInRoomEvent, MyFavouriteRoomsSearchMessageComposer, MyFrequentRoomHistorySearchMessageComposer, MyFriendsRoomsSearchMessageComposer, MyGuildBasesSearchMessageComposer, MyRecommendedRoomsMessageComposer, MyRoomHistorySearchMessageComposer, MyRoomRightsSearchMessageComposer, MyRoomsSearchMessageComposer, MysteryBoxKeysEvent, MysteryBoxWaitingCanceledMessageComposer, NavigatorCategoryListModeComposer, NavigatorCollapsedEvent, NavigatorDeleteSavedSearchComposer, NavigatorHomeRoomEvent, NavigatorInitComposer, NavigatorLiftedEvent, NavigatorMetadataEvent, NavigatorOpenRoomCreatorEvent, NavigatorSearchCloseComposer, NavigatorSearchComposer, NavigatorSearchesEvent, NavigatorSearchEvent, NavigatorSearchOpenComposer, NavigatorSearchSaveComposer, NavigatorSettingsEvent, NavigatorSettingsSaveComposer, NewConsoleMessageEvent, NewFriendRequestEvent, NewUserExperienceGetGiftsComposer, NewUserExperienceGiftOfferMessageEvent, NewUserExperienceNotCompleteEvent, NewUserExperienceScriptProceedComposer, NoobnessLevelMessageEvent, NoOwnedRoomsAlertMessageEvent, NoSuchFlatEvent, NotEnoughBalanceMessageEvent, NotificationDialogMessageEvent, NowPlayingMessageEvent, ObjectsDataUpdateEvent, ObjectsRollingEvent, OfferRewardDeliveredMessageEvent, OfficialSongIdMessageEvent, OneWayDoorStatusMessageEvent, OpenCampaignCalendarDoorAsStaffComposer, OpenCampaignCalendarDoorComposer, OpenMessageComposer, OpenMysteryTrophyMessageComposer, OpenPetPackageMessageComposer, OpenPetPackageRequestedMessageEvent, OpenPetPackageResultMessageEvent, OpenPresentComposer, OpenQuestTrackerMessageComposer, OpenWelcomeGiftComposer, OutgoingHeader, PeerUsersClassificationMessageComposer, PerformanceLogMessageComposer, PerkAllowancesMessageEvent, PetAddedToInventoryEvent, PetBreedingResultEvent, PetExperienceEvent, PetFigureUpdateEvent, PetInfoEvent, PetInventoryEvent, PetLevelNotificationEvent, PetLevelUpdateMessageEvent, PetMountComposer, PetMoveComposer, PetPlaceComposer, PetPlacingErrorEvent, PetReceivedMessageEvent, PetRemoveComposer, PetRemovedFromInventory, PetRespectComposer, PetRespectNoficationEvent, PetScratchFailedMessageEvent, PetSelectedMessageComposer, PetStatusUpdateEvent, PetSupplementComposer, PetSupplementedNotificationEvent, PetTrainingPanelMessageEvent, PhoneCollectionStateMessageEvent, PhotoCompetitionMessageComposer, PickIssuesMessageComposer, PlayListMessageEvent, PlayListSongAddedMessageEvent, PollAnswerComposer, PollContentsEvent, PollErrorEvent, PollOfferEvent, PollRejectComposer, PollStartComposer, PongMessageComposer, PopularRoomsSearchMessageComposer, PopularRoomTagsResultEvent, PostMessageMessageComposer, PostMessageMessageEvent, PostQuizAnswersComposer, PostThreadMessageEvent, PresentOpenedMessageEvent, ProductOfferEvent, PromoArticlesMessageEvent, PublishPhotoMessageComposer, PurchaseBasicMembershipExtensionComposer, PurchaseErrorMessageEvent, PurchaseFromCatalogAsGiftComposer, PurchaseFromCatalogComposer, PurchaseNotAllowedMessageEvent, PurchaseOKMessageEvent, PurchasePhotoMessageComposer, PurchaseRoomAdMessageComposer, PurchaseTargetedOfferComposer, PurchaseVipMembershipExtensionComposer, QuestCancelledMessageEvent, QuestCompletedMessageEvent, QuestDailyMessageEvent, QuestionAnsweredEvent, QuestionEvent, QuestionFinishedEvent, QuestMessageEvent, QuestsMessageEvent, QuizDataMessageEvent, QuizResultsMessageEvent, RateFlatMessageComposer, RecycleItemsMessageComposer, RecyclerFinishedMessageEvent, RecyclerStatusMessageEvent, RedeemCommunityGoalPrizeMessageComposer, RedeemItemClothingComposer, RedeemMarketplaceOfferCreditsMessageComposer, RedeemVoucherMessageComposer, RejectQuestMessageComposer, RelationshipStatusInfoEvent, ReleaseIssuesMessageComposer, RemainingMuteEvent, RemoveAllRightsMessageComposer, RemoveFriendComposer, RemoveJukeboxDiskComposer, RemoveOwnRoomRightsRoomMessageComposer, RemovePetSaddleComposer, RemoveWallItemComposer, RenderRoomMessageComposer, RenderRoomThumbnailMessageComposer, RentableSpaceCancelRentMessageComposer, RentableSpaceRentFailedMessageEvent, RentableSpaceRentMessageComposer, RentableSpaceRentOkMessageEvent, RentableSpaceStatusMessageComposer, RentableSpaceStatusMessageEvent, RequestABadgeComposer, RequestAchievementsMessageComposer, RequestBadgesComposer, RequestBotCommandConfigurationComposer, RequestCameraConfigurationComposer, RequestFriendComposer, RequestFurniInventoryWhenNotInRoomComposer, RequestPetInfoComposer, RequestPetsComposer, RequestSpamWallPostItMessageEvent, ResetPhoneNumberStateMessageComposer, ResetResolutionAchievementMessageComposer, RespectReceivedEvent, RestoreClientMessageEvent, RoomAdErrorEvent, RoomAdEventTabAdClickedComposer, RoomAdEventTabViewedComposer, RoomAdPurchaseInfoEvent, RoomAdPurchaseInitiatedComposer, RoomAdSearchMessageComposer, RoomAmbassadorAlertComposer, RoomBannedUsersComposer, RoomBanUserComposer, RoomChatlogEvent, RoomChatSettingsEvent, RoomCompetitionInitMessageComposer, RoomDeleteComposer, RoomDimmerPresetsEvent, RoomDoorbellAcceptedEvent, RoomDoorbellAccessComposer, RoomEnterComposer, RoomEnterErrorEvent, RoomEnterEvent, RoomEntryInfoMessageEvent, RoomEntryTileMessageEvent, RoomEventCancelEvent, RoomEventEvent, RoomFilterSettingsMessageEvent, RoomForwardEvent, RoomGiveRightsComposer, RoomHeightMapEvent, RoomHeightMapUpdateEvent, RoomInviteErrorEvent, RoomInviteEvent, RoomKickUserComposer, RoomMessageNotificationMessageEvent, RoomMuteComposer, RoomMuteUserComposer, RoomNetworkOpenConnectionMessageComposer, RoomOccupiedTilesMessageEvent, RoomPaintEvent, RoomPollResultEvent, RoomReadyMessageEvent, RoomRightsClearEvent, RoomRightsEvent, RoomRightsOwnerEvent, RoomScoreEvent, RoomSettingsComposer, RoomSettingsDataEvent, RoomSettingsErrorEvent, RoomSettingsSavedEvent, RoomSettingsSaveErrorEvent, RoomSettingsUpdatedEvent, RoomsWhereMyFriendsAreSearchMessageComposer, RoomsWithHighestScoreSearchMessageComposer, RoomTakeRightsComposer, RoomTextSearchMessageComposer, RoomThumbnailUpdateResultEvent, RoomUnbanUserComposer, RoomUnitActionComposer, RoomUnitBackgroundComposer, RoomUnitChatComposer, RoomUnitChatEvent, RoomUnitChatShoutComposer, RoomUnitChatShoutEvent, RoomUnitChatStyleComposer, RoomUnitChatWhisperComposer, RoomUnitChatWhisperEvent, RoomUnitDanceComposer, RoomUnitDanceEvent, RoomUnitDropHandItemComposer, RoomUnitEffectEvent, RoomUnitEvent, RoomUnitExpressionEvent, RoomUnitGiveHandItemComposer, RoomUnitGiveHandItemPetComposer, RoomUnitHandItemEvent, RoomUnitHandItemReceivedEvent, RoomUnitIdleEvent, RoomUnitInfoEvent, RoomUnitLookComposer, RoomUnitNumberEvent, RoomUnitPostureComposer, RoomUnitRemoveEvent, RoomUnitSignComposer, RoomUnitStatusEvent, RoomUnitTypingEvent, RoomUnitTypingStartComposer, RoomUnitTypingStopComposer, RoomUnitWalkComposer, RoomUsersClassificationMessageComposer, RoomUsersWithRightsComposer, RoomVisitsEvent, RoomVisualizationSettingsEvent, SanctionStatusEvent, SaveRoomSettingsComposer, SaveWardrobeOutfitMessageComposer, ScrGetKickbackInfoMessageComposer, ScrSendKickbackInfoMessageEvent, SearchFaqsMessageComposer, SeasonalCalendarDailyOfferMessageEvent, SeasonalQuestsMessageEvent, SecondsUntilMessageEvent, SelectClubGiftComposer, SellablePetPalettesMessageEvent, SendMessageComposer, SendRoomInviteComposer, SetActivatedBadgesComposer, SetClothingChangeDataMessageComposer, SetItemDataMessageComposer, SetObjectDataMessageComposer, SetPhoneNumberVerificationStatusMessageComposer, SetRelationshipStatusComposer, SetRoomSessionTagsMessageComposer, SetTargetedOfferStateComposer, SetYoutubeDisplayPlaylistMessageComposer, ShopTargetedOfferViewedComposer, ShowEnforceRoomCategoryDialogEvent, ShowMysteryBoxWaitMessageEvent, SimpleAlertMessageEvent, SSOTicketMessageComposer, StartCampaignMessageComposer, StartRoomPollEvent, SubmitRoomToCompetitionMessageComposer, TalentLevelUpEvent, TalentTrackComposer, TalentTrackLevelMessageEvent, TalentTrackMessageEvent, TargetedOfferEvent, TargetedOfferNotFoundEvent, ThreadMessagesMessageEvent, ThumbnailStatusMessageEvent, TogglePetBreedingComposer, TogglePetRidingComposer, ToggleStaffPickMessageComposer, TradingAcceptComposer, TradingAcceptEvent, TradingCancelComposer, TradingCloseComposer, TradingCloseEvent, TradingCompletedEvent, TradingConfirmationComposer, TradingConfirmationEvent, TradingListAddItemComposer, TradingListAddItemsComposer, TradingListItemEvent, TradingListItemRemoveComposer, TradingNoSuchItemEvent, TradingNotOpenEvent, TradingOpenComposer, TradingOpenEvent, TradingOpenFailedEvent, TradingOtherNotAllowedEvent, TradingUnacceptComposer, TradingYouAreNotAllowedEvent, TraxSongInfoMessageEvent, TryPhoneNumberMessageComposer, TryPhoneNumberResultMessageEvent, TryVerificationCodeResultMessageEvent, UnblockGroupMemberMessageComposer, UnignoreUserComposer, UniqueIDMessageComposer, UnloadGameMessageEvent, UnreadForumsCountMessageEvent, UnseenItemsEvent, UnseenResetCategoryComposer, UnseenResetItemsComposer, UpdateActionMessageComposer, UpdateConditionMessageComposer, UpdateFloorPropertiesMessageComposer, UpdateForumReadMarkerMessageComposer, UpdateForumSettingsMessageComposer, UpdateFurniturePositionComposer, UpdateHomeRoomMessageComposer, UpdateMessageMessageEvent, UpdateRoomCategoryAndTradeSettingsComposer, UpdateRoomFilterMessageComposer, UpdateRoomThumbnailMessageComposer, UpdateThreadMessageComposer, UpdateThreadMessageEvent, UpdateTriggerMessageComposer, UsePetProductComposer, UserBannedMessageEvent, UserChatlogEvent, UserClassificationMessageEvent, UserCreditsEvent, UserCurrencyComposer, UserCurrencyEvent, UserCurrentBadgesComposer, UserCurrentBadgesEvent, UserEventCatsEvent, UserFigureComposer, UserFlatCatsEvent, UserGameAchievementsMessageEvent, UserInfoEvent, UserMottoComposer, UserNameChangeMessageEvent, UserPermissionsEvent, UserPermissionsMapEvent, UserProfileComposer, UserProfileEvent, UserRelationshipsComposer, UserRespectComposer, UserSettingsCameraFollowComposer, UserSettingsEvent, UserSettingsOldChatComposer, UserSettingsRoomInvitesComposer, UserSettingsSoundComposer, UserSongDisksInventoryMessageEvent, UserSubscriptionComposer, UserSubscriptionEvent, UserTagsMessageEvent, UserUnbannedFromRoomEvent, UserWardrobePageEvent, VerifyCodeMessageComposer, VersionCheckMessageComposer, VisitUserComposer, VoteForRoomMessageComposer, VotePollCounterMessageComposer, VoucherRedeemErrorMessageEvent, VoucherRedeemOkMessageEvent, WardrobeMessageEvent, WeeklyCompetitiveFriendsLeaderboardEvent, WeeklyCompetitiveLeaderboardEvent, WeeklyGameRewardEvent, WeeklyGameRewardWinnersEvent, WelcomeGiftChangeEmailComposer, WelcomeGiftChangeEmailResultEvent, WelcomeGiftStatusEvent, WiredFurniActionEvent, WiredFurniConditionEvent, WiredFurniTriggerEvent, WiredOpenEvent, WiredRewardResultMessageEvent, WiredSaveSuccessEvent, WiredValidationErrorEvent, YouArePlayingGameEvent, YouAreSpectatorMessageEvent, YoutubeControlVideoMessageEvent, YoutubeDisplayPlaylistsEvent, YoutubeDisplayVideoMessageEvent, ClickUserMessageComposer } from './messages'; import { PurchaseCatalogPrefixComposer, PurchaseNickIconComposer, PurchasePrefixComposer, RequestNickIconsComposer, SetActiveNickIconComposer, SetActivePrefixComposer, SetDisplayOrderComposer, UserNickIconsEvent } from './messages'; import { WiredMonitorDataEvent, WiredMonitorRequestComposer, WiredRoomSettingsDataEvent, WiredRoomSettingsRequestComposer, WiredRoomSettingsSaveComposer, WiredUserInspectMoveComposer, WiredUserVariableManageComposer, WiredUserVariableUpdateComposer, WiredUserVariablesDataEvent, WiredUserVariablesRequestComposer } from './messages'; import { WiredMovementsEvent } from './messages'; @@ -533,6 +533,7 @@ export class NitroMessages implements IMessageConfiguration this._events.set(IncomingHeader.USER_IGNORED_RESULT, IgnoreResultEvent); this._events.set(IncomingHeader.USER_RESPECT, RespectReceivedEvent); this._events.set(IncomingHeader.USER_PERMISSIONS, UserPermissionsEvent); + this._events.set(IncomingHeader.USER_PERMISSIONS_MAP, UserPermissionsMapEvent); this._events.set(IncomingHeader.USER_BADGES_CURRENT, UserCurrentBadgesEvent); this._events.set(IncomingHeader.USER_INFO, UserInfoEvent); this._events.set(IncomingHeader.UNIT_CHANGE_NAME, UserNameChangeMessageEvent); diff --git a/packages/communication/src/messages/incoming/IncomingHeader.ts b/packages/communication/src/messages/incoming/IncomingHeader.ts index 70dbad3..6a5f7dd 100644 --- a/packages/communication/src/messages/incoming/IncomingHeader.ts +++ b/packages/communication/src/messages/incoming/IncomingHeader.ts @@ -255,6 +255,8 @@ export class IncomingHeader public static USER_OUTFITS = 3315; public static USER_PERKS = 2586; public static USER_PERMISSIONS = 411; + // Resolved permission map (Arcturus extension, Outgoing.UserPermissionsMapComposer = 10070). + public static USER_PERMISSIONS_MAP = 10070; public static USER_PET_ADD = 2101; public static USER_PET_REMOVE = 3253; public static USER_PETS = 3522; diff --git a/packages/communication/src/messages/incoming/user/access/UserPermissionsMapEvent.ts b/packages/communication/src/messages/incoming/user/access/UserPermissionsMapEvent.ts new file mode 100644 index 0000000..b0653cb --- /dev/null +++ b/packages/communication/src/messages/incoming/user/access/UserPermissionsMapEvent.ts @@ -0,0 +1,16 @@ +import { IMessageEvent } from '@nitrots/api'; +import { MessageEvent } from '@nitrots/events'; +import { UserPermissionsMapParser } from '../../../parser'; + +export class UserPermissionsMapEvent extends MessageEvent implements IMessageEvent +{ + constructor(callBack: Function) + { + super(callBack, UserPermissionsMapParser); + } + + public getParser(): UserPermissionsMapParser + { + return this.parser as UserPermissionsMapParser; + } +} diff --git a/packages/communication/src/messages/incoming/user/access/index.ts b/packages/communication/src/messages/incoming/user/access/index.ts index 51901c3..3d07ab1 100644 --- a/packages/communication/src/messages/incoming/user/access/index.ts +++ b/packages/communication/src/messages/incoming/user/access/index.ts @@ -1 +1,2 @@ export * from './UserPermissionsEvent'; +export * from './UserPermissionsMapEvent'; diff --git a/packages/communication/src/messages/parser/user/access/UserPermissionsMapParser.ts b/packages/communication/src/messages/parser/user/access/UserPermissionsMapParser.ts new file mode 100644 index 0000000..2e23a8e --- /dev/null +++ b/packages/communication/src/messages/parser/user/access/UserPermissionsMapParser.ts @@ -0,0 +1,54 @@ +import { IMessageDataWrapper, IMessageParser } from '@nitrots/api'; + +/** + * Parses the resolved permission map for the current user + * (Arcturus-Morningstar-Extended ≥ 4.2.10, header + * `Outgoing.UserPermissionsMapComposer = 10070`). + * + * Wire layout: + * int count + * loop: string permission_key + int value (1 = ALLOWED, 2 = ROOM_OWNER) + * + * Only permissions whose `PermissionSetting != DISALLOWED` are sent — + * absence means "no". The renderer-side `SessionDataManager` consumes + * this and exposes it via a snapshot getter; React-side + * `useHasPermission(key)` drives UI gates against the real + * `permission_definitions.permission_key` strings instead of + * deployment-specific rank IDs. + */ +export class UserPermissionsMapParser implements IMessageParser +{ + private _permissions: Map = new Map(); + + public flush(): boolean + { + this._permissions = new Map(); + + return true; + } + + public parse(wrapper: IMessageDataWrapper): boolean + { + if(!wrapper) return false; + + const count = wrapper.readInt(); + const fresh = new Map(); + + for(let i = 0; i < count; i++) + { + const key = wrapper.readString(); + const value = wrapper.readInt(); + + fresh.set(key, value); + } + + this._permissions = fresh; + + return true; + } + + public get permissions(): ReadonlyMap + { + return this._permissions; + } +} diff --git a/packages/communication/src/messages/parser/user/access/index.ts b/packages/communication/src/messages/parser/user/access/index.ts index 7cb9b64..43a3c84 100644 --- a/packages/communication/src/messages/parser/user/access/index.ts +++ b/packages/communication/src/messages/parser/user/access/index.ts @@ -1 +1,2 @@ +export * from './UserPermissionsMapParser'; export * from './UserPermissionsParser'; diff --git a/packages/events/src/NitroEventType.ts b/packages/events/src/NitroEventType.ts index b7a1ea0..af4e26c 100644 --- a/packages/events/src/NitroEventType.ts +++ b/packages/events/src/NitroEventType.ts @@ -23,4 +23,5 @@ export class NitroEventType public static readonly GROUP_BADGES_UPDATED = 'GROUP_BADGES_UPDATED'; public static readonly ROOM_USER_LIST_UPDATED = 'ROOM_USER_LIST_UPDATED'; public static readonly SOUND_VOLUMES_UPDATED = 'SOUND_VOLUMES_UPDATED'; + public static readonly USER_PERMISSIONS_UPDATED = 'USER_PERMISSIONS_UPDATED'; } diff --git a/packages/session/src/SessionDataManager.ts b/packages/session/src/SessionDataManager.ts index c6c8b9b..213c2a1 100644 --- a/packages/session/src/SessionDataManager.ts +++ b/packages/session/src/SessionDataManager.ts @@ -1,5 +1,5 @@ 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 { AccountSafetyLockStatusChangeMessageEvent, AccountSafetyLockStatusChangeParser, AvailabilityStatusMessageEvent, ChangeUserNameResultMessageEvent, EmailStatusResultEvent, FigureUpdateEvent, GetCommunication, GetUserTagsComposer, InClientLinkEvent, MysteryBoxKeysEvent, NoobnessLevelMessageEvent, PetRespectComposer, PetScratchFailedMessageEvent, RoomReadyMessageEvent, RoomUnitChatComposer, UserInfoEvent, UserNameChangeMessageEvent, UserPermissionsEvent, UserPermissionsMapEvent, UserRespectComposer, UserTagsMessageEvent } from '@nitrots/communication'; import { GetConfiguration } from '@nitrots/configuration'; import { GetLocalizationManager } from '@nitrots/localization'; import { GetEventDispatcher, MysteryBoxKeysUpdateEvent, NitroEvent, NitroEventType, NitroSettingsEvent, SessionDataPreferencesEvent, UserNameUpdateEvent } from '@nitrots/events'; @@ -59,6 +59,9 @@ export class SessionDataManager implements ISessionDataManager private _userDataSnapshot: Readonly | null = null; + private _permissions: Map = new Map(); + private _permissionsSnapshot: ReadonlyMap | null = null; + constructor() { this.resetUserInfo(); @@ -71,6 +74,36 @@ export class SessionDataManager implements ISessionDataManager GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SESSION_DATA_UPDATED)); } + private invalidatePermissionsSnapshot(): void + { + this._permissionsSnapshot = null; + + GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.USER_PERMISSIONS_UPDATED)); + } + + /** + * Resolved permission map for the current user — mirror of + * `permission_definitions` for the user's rank, filtered to keys + * with `PermissionSetting != DISALLOWED`. Wire-fed by + * `UserPermissionsMapEvent` (Arcturus ≥ 4.2.10). Older emulators + * that don't ship the new packet leave the snapshot empty; React + * consumers via `useHasPermission(key)` then degrade gracefully + * (every gate returns false → mod UI hidden, which is the safe + * default). + * + * Referentially stable until the next + * `UserPermissionsMapEvent` arrives (e.g. after + * `HabboManager.setRank`). + */ + public getPermissionsSnapshot(): ReadonlyMap + { + if(this._permissionsSnapshot) return this._permissionsSnapshot; + + this._permissionsSnapshot = new Map(this._permissions) as ReadonlyMap; + + return this._permissionsSnapshot; + } + public getUserDataSnapshot(): Readonly { if(this._userDataSnapshot) return this._userDataSnapshot; @@ -128,6 +161,7 @@ export class SessionDataManager implements ISessionDataManager })), GetCommunication().registerMessageEvent(new UserInfoEvent(this.onUserInfoEvent.bind(this))), GetCommunication().registerMessageEvent(new UserPermissionsEvent(this.onUserPermissionsEvent.bind(this))), + GetCommunication().registerMessageEvent(new UserPermissionsMapEvent(this.onUserPermissionsMapEvent.bind(this))), GetCommunication().registerMessageEvent(new AvailabilityStatusMessageEvent(this.onAvailabilityStatusMessageEvent.bind(this))), GetCommunication().registerMessageEvent(new PetScratchFailedMessageEvent(this.onPetRespectFailed.bind(this))), GetCommunication().registerMessageEvent(new ChangeUserNameResultMessageEvent(this.onChangeNameUpdateEvent.bind(this))), @@ -263,6 +297,17 @@ export class SessionDataManager implements ISessionDataManager this.invalidateUserDataSnapshot(); } + private onUserPermissionsMapEvent(event: UserPermissionsMapEvent): void + { + if(!event || !event.connection) return; + + // Copy into our local mutable Map so the parser's reference (which + // is overwritten on every parse() call) can't leak back to consumers. + this._permissions = new Map(event.getParser().permissions); + + this.invalidatePermissionsSnapshot(); + } + private onAvailabilityStatusMessageEvent(event: AvailabilityStatusMessageEvent): void { if(!event || !event.connection) return; From 221f186d616436701ef38a18e28a1735bc1190e9 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Tue, 19 May 2026 19:39:49 +0200 Subject: [PATCH 25/30] refactor(session): fold permission map into UserPermissionsEvent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the separate UserPermissionsMapEvent / UserPermissionsMapParser and the IncomingHeader.USER_PERMISSIONS_MAP = 10070 registration — the resolved permission map now rides on the existing UserPermissionsEvent as a third optional trailing block, after the rank metadata one. Same wire data, one fewer packet, one fewer event registration, one fewer handler. Wire layout (UserPermissionsEvent / header 411): int clubLevel int securityLevel bool isAmbassador --- rank metadata (Arcturus ≥ 4.2.10) --- int rankId string rankName string rankBadge string rankPrefix string rankPrefixColor --- resolved permission map (Arcturus ≥ 4.2.10) --- int count loop: string permission_key + int value (1=ALLOWED, 2=ROOM_OWNER) Both trailing blocks are guarded by `bytesAvailable` in UserPermissionsParser so older emulators that don't append them still parse cleanly. SessionDataManager.onUserPermissionsEvent is now the single handler: - updates clubLevel/securityLevel/isAmbassador/rank* AND _permissions; - invalidates BOTH the user-data snapshot and the permissions snapshot (dispatching the two distinct NitroEventType.SESSION_DATA_UPDATED / USER_PERMISSIONS_UPDATED events). The two distinct invalidation events stay so React consumers can subscribe granularly — useHasPermission(key) only triggers on a real permission map flip, not on every session-data bump. Companion Arcturus change (feat/react19-emu-update) folds UserPermissionsMapComposer into UserPermissionsComposer and removes the second sendResponse in HabboManager.setRank + SecureLoginEvent. Verification: yarn compile:fast clean, vitest 138/138. --- packages/communication/src/NitroMessages.ts | 3 +- .../src/messages/incoming/IncomingHeader.ts | 2 - .../user/access/UserPermissionsMapEvent.ts | 16 ------ .../messages/incoming/user/access/index.ts | 1 - .../user/access/UserPermissionsMapParser.ts | 54 ------------------- .../user/access/UserPermissionsParser.ts | 32 +++++++++-- .../src/messages/parser/user/access/index.ts | 1 - packages/session/src/SessionDataManager.ts | 24 ++++----- 8 files changed, 42 insertions(+), 91 deletions(-) delete mode 100644 packages/communication/src/messages/incoming/user/access/UserPermissionsMapEvent.ts delete mode 100644 packages/communication/src/messages/parser/user/access/UserPermissionsMapParser.ts diff --git a/packages/communication/src/NitroMessages.ts b/packages/communication/src/NitroMessages.ts index 368a5bf..3caca87 100644 --- a/packages/communication/src/NitroMessages.ts +++ b/packages/communication/src/NitroMessages.ts @@ -1,5 +1,5 @@ import { IMessageConfiguration } from '@nitrots/api'; -import { AcceptFriendMessageComposer, AcceptFriendResultEvent, AcceptGameInviteMessageComposer, AcceptQuestMessageComposer, AccountSafetyLockStatusChangeMessageEvent, AchievementEvent, AchievementNotificationMessageEvent, AchievementResolutionCompletedMessageEvent, AchievementResolutionProgressMessageEvent, AchievementResolutionsMessageEvent, AchievementsEvent, AchievementsScoreEvent, ActivateQuestMessageComposer, ActivityPointNotificationMessageEvent, AddFavouriteRoomMessageComposer, AddJukeboxDiskComposer, AddSpamWallPostItMessageComposer, ApplySnapshotMessageComposer, ApplyTonerComposer, ApproveAllMembershipRequestsMessageComposer, ApproveNameMessageComposer, ApproveNameMessageEvent, AreaHideMessageEvent, AuthenticatedEvent, AuthenticationMessageComposer, AvailabilityStatusMessageEvent, AvailabilityTimeMessageEvent, AvailableCommandsEvent, AvatarEffectActivatedComposer, AvatarEffectActivatedEvent, AvatarEffectAddedEvent, AvatarEffectExpiredEvent, AvatarEffectSelectedComposer, AvatarEffectSelectedEvent, AvatarEffectsEvent, BadgePointLimitsEvent, BadgeReceivedEvent, BadgesEvent, BannedUsersFromRoomEvent, BonusRareInfoMessageEvent, BotAddedToInventoryEvent, BotCommandConfigurationEvent, BotErrorEvent, BotForceOpenContextMenuEvent, BotInventoryMessageEvent, BotPlaceComposer, BotReceivedMessageEvent, BotRemoveComposer, BotRemovedFromInventoryEvent, BotSkillListUpdateEvent, BotSkillSaveComposer, BreedPetsMessageComposer, BuildersClubFurniCountMessageEvent, BuildersClubPlaceRoomItemMessageComposer, BuildersClubPlaceWallItemMessageComposer, BuildersClubQueryFurniCountMessageComposer, BuildersClubSubscriptionStatusMessageEvent, BundleDiscountRulesetMessageEvent, BuyMarketplaceOfferMessageComposer, BuyMarketplaceTokensMessageComposer, CallForHelpDisabledNotifyMessageEvent, CallForHelpFromForumMessageMessageComposer, CallForHelpFromForumThreadMessageComposer, CallForHelpFromIMMessageComposer, CallForHelpFromPhotoMessageComposer, CallForHelpFromSelfieMessageComposer, CallForHelpMessageComposer, CallForHelpPendingCallsDeletedMessageEvent, CallForHelpPendingCallsMessageEvent, CallForHelpReplyMessageEvent, CallForHelpResultMessageEvent, CameraPublishStatusMessageEvent, CameraPurchaseOKMessageEvent, CameraSnapshotMessageEvent, CameraStorageUrlMessageEvent, CampaignCalendarDataMessageEvent, CampaignCalendarDoorOpenedMessageEvent, CancelEventMessageComposer, CancelMarketplaceOfferMessageComposer, CancelMysteryBoxWaitMessageEvent, CancelPetBreedingComposer, CancelQuestMessageComposer, CanCreateRoomEvent, CanCreateRoomEventEvent, CanCreateRoomMessageComposer, CatalogAdminCreateOfferComposer, CatalogAdminCreatePageComposer, CatalogAdminDeleteOfferComposer, CatalogAdminDeletePageComposer, CatalogAdminMoveOfferComposer, CatalogAdminMovePageComposer, CatalogAdminPublishComposer, CatalogAdminResultEvent, CatalogAdminSaveOfferComposer, CatalogAdminSavePageComposer, CatalogGroupsComposer, CatalogPageExpirationEvent, CatalogPageMessageEvent, CatalogPagesListEvent, CatalogPageWithEarliestExpiryMessageEvent, CatalogPublishedMessageEvent, CategoriesWithVisitorCountEvent, CfhChatlogEvent, CfhSanctionMessageEvent, CfhTopicsInitEvent, ChangeEmailComposer, ChangeEmailResultEvent, ChangeQueueMessageComposer, ChangeUserNameMessageComposer, ChangeUserNameResultMessageEvent, ChatReviewGuideDecidesOnOfferMessageComposer, ChatReviewGuideDetachedMessageComposer, ChatReviewGuideVoteMessageComposer, ChatReviewSessionCreateMessageComposer, ChatReviewSessionDetachedMessageEvent, ChatReviewSessionOfferedToGuideMessageEvent, ChatReviewSessionResultsMessageEvent, ChatReviewSessionStartedMessageEvent, ChatReviewSessionVotingStatusMessageEvent, CheckUserNameMessageComposer, CheckUserNameResultMessageEvent, ClickFurniMessageComposer, ClientHelloMessageComposer, ClientPingEvent, CloseIssueDefaultActionMessageComposer, CloseIssuesMessageComposer, ClubGiftInfoEvent, ClubGiftNotificationEvent, ClubGiftSelectedEvent, CommunityGoalEarnedPrizesMessageEvent, CommunityGoalHallOfFameMessageEvent, CommunityGoalProgressMessageEvent, CommunityGoalVoteMessageComposer, CommunityGoalVoteMessageEvent, CompetitionEntrySubmitResultEvent, CompetitionRoomsDataMessageEvent, CompetitionRoomsSearchMessageComposer, CompetitionStatusMessageEvent, CompetitionVotingInfoMessageEvent, CompleteDiffieHandshakeEvent, CompleteDiffieHandshakeMessageComposer, CompostPlantMessageComposer, ConcurrentUsersGoalProgressMessageEvent, ConfirmPetBreedingComposer, ConnectionErrorEvent, ControlYoutubeDisplayPlaybackMessageComposer, ConvertedRoomIdEvent, ConvertGlobalRoomIdMessageComposer, CraftableProductsEvent, CraftComposer, CraftingRecipeEvent, CraftingRecipesAvailableEvent, CraftingResultEvent, CraftSecretComposer, CreateFlatMessageComposer, CurrentTimingCodeMessageEvent, CustomUserNotificationMessageEvent, DeclineFriendMessageComposer, DefaultSanctionMessageComposer, DeleteBadgeMessageComposer, DeleteFavouriteRoomMessageComposer, DeleteItemMessageComposer, DeletePendingCallsForHelpMessageComposer, DeletePetMessageComposer, DesktopViewComposer, DesktopViewEvent, DiceValueMessageEvent, DirectSMSClubBuyAvailableMessageEvent, DisconnectMessageComposer, DisconnectReasonEvent, DoorbellMessageEvent, EditEventMessageComposer, ElementPointerMessageEvent, EmailStatusResultEvent, EpicPopupMessageEvent, ExtendedProfileChangedMessageEvent, ExtendRentOrBuyoutFurniMessageComposer, ExtendRentOrBuyoutStripItemMessageComposer, FavoriteMembershipUpdateMessageEvent, FavouriteChangedEvent, FavouritesEvent, FigureSetIdsMessageEvent, FigureUpdateEvent, FindFriendsProcessResultEvent, FindNewFriendsMessageComposer, FireworkChargeDataEvent, FlatAccessDeniedMessageEvent, FlatControllerAddedEvent, FlatControllerRemovedEvent, FlatControllersEvent, FlatCreatedEvent, FloodControlEvent, FloorHeightMapEvent, FollowFriendFailedEvent, FollowFriendMessageComposer, ForumDataMessageEvent, ForumsListMessageEvent, ForwardToACompetitionRoomMessageComposer, ForwardToARandomPromotedRoomMessageComposer, ForwardToASubmittableRoomMessageComposer, ForwardToRandomCompetitionRoomMessageComposer, ForwardToSomeRoomMessageComposer, FriendFurniConfirmLockMessageComposer, FriendListFragmentEvent, FriendListUpdateComposer, FriendListUpdateEvent, FriendNotificationEvent, FriendRequestQuestCompleteMessageComposer, FriendRequestsEvent, FurniRentOrBuyoutOfferMessageEvent, FurnitureAliasesComposer, FurnitureAliasesEvent, FurnitureColorWheelComposer, FurnitureDataEvent, FurnitureDiceActivateComposer, FurnitureDiceDeactivateComposer, FurnitureExchangeComposer, FurnitureFloorAddEvent, FurnitureFloorEvent, FurnitureFloorRemoveEvent, FurnitureFloorUpdateComposer, FurnitureFloorUpdateEvent, FurnitureGroupInfoComposer, FurnitureListAddOrUpdateEvent, FurnitureListComposer, FurnitureListEvent, FurnitureListInvalidateEvent, FurnitureListRemovedEvent, FurnitureMannequinSaveLookComposer, FurnitureMannequinSaveNameComposer, FurnitureMultiStateComposer, FurnitureOneWayDoorComposer, FurniturePickupAllComposer, FurniturePickupComposer, FurniturePlaceComposer, FurniturePlacePaintComposer, FurniturePostItPlaceComposer, FurniturePostItPlacedEvent, FurnitureRandomStateComposer, FurnitureStackHeightComposer, FurnitureStackHeightEvent, FurnitureWallAddEvent, FurnitureWallEvent, FurnitureWallMultiStateComposer, FurnitureWallRemoveEvent, FurnitureWallUpdateComposer, FurnitureWallUpdateEvent, FurniEditorBySpriteComposer, FurniEditorDeleteComposer, FurniEditorDetailComposer, FurniEditorDetailResultEvent, FurniEditorInteractionsComposer, FurniEditorInteractionsResultEvent, FurniEditorResultEvent, FurniEditorSearchComposer, FurniEditorSearchResultEvent, FurniEditorUpdateComposer, Game2AccountGameStatusMessageEvent, Game2CheckGameDirectoryStatusMessageComposer, Game2ExitGameMessageComposer, Game2GameChatMessageComposer, Game2GameDirectoryStatusMessageEvent, Game2GetAccountGameStatusMessageComposer, Game2GetWeeklyFriendsLeaderboardComposer, Game2GetWeeklyLeaderboardComposer, Game2InArenaQueueMessageEvent, Game2JoiningGameFailedMessageEvent, Game2LoadStageReadyMessageComposer, Game2PlayAgainMessageComposer, Game2RequestFullStatusUpdateMessageComposer, Game2StartingGameFailedMessageEvent, Game2StopCounterMessageEvent, Game2UserLeftGameMessageEvent, Game2WeeklyFriendsLeaderboardEvent, Game2WeeklyLeaderboardEvent, GameAchievementsMessageEvent, GameInviteMessageEvent, GameListMessageEvent, GameStatusMessageEvent, GameUnloadedMessageComposer, GenericErrorEvent, GetBadgePointLimitsComposer, GetBonusRareInfoMessageComposer, GetBotInventoryComposer, GetBundleDiscountRulesetComposer, GetCatalogIndexComposer, GetCatalogPageComposer, GetCatalogPageExpirationComposer, GetCatalogPageWithEarliestExpiryComposer, GetCategoriesWithUserCountMessageComposer, GetCfhChatlogMessageComposer, GetCfhStatusMessageComposer, GetClubGiftInfo, GetClubOffersMessageComposer, GetCommunityGoalEarnedPrizesMessageComposer, GetCommunityGoalHallOfFameMessageComposer, GetCommunityGoalProgressMessageComposer, GetConcurrentUsersGoalProgressMessageComposer, GetConcurrentUsersRewardMessageComposer, GetCraftableProductsComposer, GetCraftingRecipeComposer, GetCraftingRecipesAvailableComposer, GetCurrentTimingCodeMessageComposer, GetCustomRoomFilterMessageComposer, GetDailyQuestMessageComposer, GetDirectClubBuyAvailableComposer, GetEmailStatusComposer, GetExtendedProfileByNameMessageComposer, GetFaqCategoryMessageComposer, GetFaqTextMessageComposer, GetForumsListMessageComposer, GetForumStatsMessageComposer, GetFriendRequestsComposer, GetGameAchievementsMessageComposer, GetGameListMessageComposer, GetGameStatusMessageComposer, GetGiftMessageComposer, GetGiftWrappingConfigurationComposer, GetGuestRoomMessageComposer, GetGuestRoomResultEvent, GetGuideReportingStatusMessageComposer, GetHabboBasicMembershipExtendOfferComposer, GetHabboClubExtendOfferMessageComposer, GetHabboGroupBadgesMessageComposer, GetIgnoredUsersComposer, GetInterstitialMessageComposer, GetIsBadgeRequestFulfilledComposer, GetIsOfferGiftableComposer, GetIsUserPartOfCompetitionMessageComposer, GetItemDataComposer, GetJukeboxPlayListMessageComposer, GetLimitedOfferAppearingNextComposer, GetMarketplaceCanMakeOfferComposer, GetMarketplaceConfigurationMessageComposer, GetMarketplaceItemStatsComposer, GetMarketplaceOffersMessageComposer, GetMarketplaceOwnOffersMessageComposer, GetMessagesMessageComposer, GetModeratorRoomInfoMessageComposer, GetModeratorUserInfoMessageComposer, GetNextTargetedOfferComposer, GetNowPlayingMessageComposer, GetOccupiedTilesMessageComposer, GetOfficialRoomsMessageComposer, GetOfficialSongIdMessageComposer, GetPendingCallsForHelpMessageComposer, GetPetCommandsComposer, GetPopularRoomTagsMessageComposer, GetProductOfferComposer, GetPromoArticlesComposer, GetQuestsMessageComposer, GetQuizQuestionsComposer, GetRecyclerStatusMessageComposer, GetRentOrBuyoutOfferMessageComposer, GetResolutionAchievementsMessageComposer, GetRoomAdPurchaseInfoComposer, GetRoomChatlogMessageComposer, GetRoomEntryDataMessageComposer, GetRoomEntryTileMessageComposer, GetRoomVisitsMessageComposer, GetSeasonalCalendarDailyOfferComposer, GetSeasonalQuestsOnlyMessageComposer, GetSecondsUntilMessageComposer, GetSellablePetPalettesComposer, GetSongInfoMessageComposer, GetSoundMachinePlayListMessageComposer, GetSoundSettingsComposer, GetTalentTrackLevelMessageComposer, GetTargetedOfferComposer, GetThreadMessageComposer, GetThreadsMessageComposer, GetUnreadForumsCountMessageComposer, GetUserChatlogMessageComposer, GetUserEventCatsMessageComposer, GetUserFlatCatsMessageComposer, GetUserGameAchievementsMessageComposer, GetUserSongDisksMessageComposer, GetUserTagsComposer, GetWardrobeMessageComposer, GetWeeklyGameRewardComposer, GetWeeklyGameRewardWinnersComposer, GetYoutubeDisplayStatusMessageComposer, GiftReceiverNotFoundEvent, GiftWrappingConfigurationEvent, GotMysteryBoxPrizeMessageEvent, GoToFlatMessageComposer, GroupAdminGiveComposer, GroupAdminTakeComposer, GroupBadgePartsComposer, GroupBadgePartsEvent, GroupBuyComposer, GroupBuyDataComposer, GroupBuyDataEvent, GroupConfirmMemberRemoveEvent, GroupConfirmRemoveMemberComposer, GroupDeleteComposer, GroupDetailsChangedMessageEvent, GroupFavoriteComposer, GroupFurniContextMenuInfoMessageEvent, GroupInformationComposer, GroupInformationEvent, GroupJoinComposer, GroupMembersComposer, GroupMembersEvent, GroupMembershipAcceptComposer, GroupMembershipDeclineComposer, GroupMembershipRequestedMessageEvent, GroupPurchasedEvent, GroupRemoveMemberComposer, GroupSaveBadgeComposer, GroupSaveColorsComposer, GroupSaveInformationComposer, GroupSavePreferencesComposer, GroupSettingsComposer, GroupSettingsEvent, GroupUnfavoriteComposer, GuestRoomSearchResultEvent, GuideOnDutyStatusMessageEvent, GuideReportingStatusMessageEvent, GuideSessionAttachedMessageEvent, GuideSessionCreateMessageComposer, GuideSessionDetachedMessageEvent, GuideSessionEndedMessageEvent, GuideSessionErrorMessageEvent, GuideSessionFeedbackMessageComposer, GuideSessionGetRequesterRoomMessageComposer, GuideSessionGuideDecidesMessageComposer, GuideSessionInvitedToGuideRoomMessageEvent, GuideSessionInviteRequesterMessageComposer, GuideSessionIsTypingMessageComposer, GuideSessionMessageMessageComposer, GuideSessionMessageMessageEvent, GuideSessionOnDutyUpdateMessageComposer, GuideSessionPartnerIsTypingMessageEvent, GuideSessionReportMessageComposer, GuideSessionRequesterCancelsMessageComposer, GuideSessionRequesterRoomMessageEvent, GuideSessionResolvedMessageComposer, GuideSessionStartedMessageEvent, GuideTicketCreationResultMessageEvent, GuideTicketResolutionMessageEvent, GuildBaseSearchMessageComposer, GuildEditFailedMessageEvent, GuildForumThreadsEvent, GuildMemberMgmtFailedMessageEvent, GuildMembershipsMessageEvent, HabboBroadcastMessageEvent, HabboClubExtendOfferMessageEvent, HabboClubOffersMessageEvent, HabboGroupBadgesMessageEvent, HabboGroupDeactivatedMessageEvent, HabboGroupJoinFailedMessageEvent, HabboSearchComposer, HabboSearchResultEvent, HarvestPetMessageComposer, HotelClosedAndOpensEvent, HotelClosesAndWillOpenAtEvent, HotelMergeNameChangeEvent, HotelWillCloseInMinutesEvent, IdentityAccountsEvent, IgnoredUsersEvent, IgnoreResultEvent, IgnoreUserComposer, IgnoreUserIdComposer, InClientLinkEvent, IncomingHeader, InfoFeedEnableMessageEvent, InfoRetrieveMessageComposer, InitCameraMessageEvent, InitDiffieHandshakeEvent, InitDiffieHandshakeMessageComposer, InstantMessageErrorEvent, InterstitialMessageEvent, InterstitialShownMessageComposer, IsBadgeRequestFulfilledEvent, IsOfferGiftableMessageEvent, IssueCloseNotificationMessageEvent, IssueDeletedMessageEvent, IssueInfoMessageEvent, IssuePickFailedMessageEvent, IsUserPartOfCompetitionMessageEvent, ItemDataUpdateMessageEvent, JoinedQueueMessageEvent, JoiningQueueFailedMessageEvent, JoinQueueMessageComposer, JukeboxPlayListFullMessageEvent, JukeboxSongDisksMessageEvent, LagWarningReportMessageComposer, LeaveQueueMessageComposer, LeftQueueMessageEvent, LimitedEditionSoldOutEvent, LimitedOfferAppearingNextMessageEvent, LoadGameMessageEvent, LoadGameUrlEvent, LoveLockFurniFinishedEvent, LoveLockFurniFriendConfirmedEvent, LoveLockFurniStartEvent, MaintenanceStatusMessageEvent, MakeOfferMessageComposer, MarkCatalogNewAdditionsPageOpenedComposer, MarketplaceBuyOfferResultEvent, MarketplaceCancelOfferResultEvent, MarketplaceCanMakeOfferResult, MarketplaceConfigurationEvent, MarketplaceItemStatsEvent, MarketplaceMakeOfferResult, MarketPlaceOffersEvent, MarketplaceOwnOffersEvent, MessageErrorEvent, MessengerInitComposer, MessengerInitEvent, MiniMailNewMessageEvent, MiniMailUnreadCountEvent, ModAlertMessageComposer, ModBanMessageComposer, ModerateMessageMessageComposer, ModerateRoomMessageComposer, ModerateThreadMessageComposer, ModeratorActionMessageComposer, ModeratorActionResultMessageEvent, ModeratorCautionEvent, ModeratorInitMessageEvent, ModeratorMessageEvent, ModeratorRoomInfoEvent, ModeratorToolPreferencesEvent, ModeratorUserInfoEvent, ModKickMessageComposer, ModMessageMessageComposer, ModMuteMessageComposer, ModToolPreferencesComposer, ModToolSanctionComposer, ModTradingLockMessageComposer, MoodlightSettingsComposer, MoodlightSettingsSaveComposer, MoodlightTogggleStateComposer, MOTDNotificationEvent, MuteAllInRoomEvent, MyFavouriteRoomsSearchMessageComposer, MyFrequentRoomHistorySearchMessageComposer, MyFriendsRoomsSearchMessageComposer, MyGuildBasesSearchMessageComposer, MyRecommendedRoomsMessageComposer, MyRoomHistorySearchMessageComposer, MyRoomRightsSearchMessageComposer, MyRoomsSearchMessageComposer, MysteryBoxKeysEvent, MysteryBoxWaitingCanceledMessageComposer, NavigatorCategoryListModeComposer, NavigatorCollapsedEvent, NavigatorDeleteSavedSearchComposer, NavigatorHomeRoomEvent, NavigatorInitComposer, NavigatorLiftedEvent, NavigatorMetadataEvent, NavigatorOpenRoomCreatorEvent, NavigatorSearchCloseComposer, NavigatorSearchComposer, NavigatorSearchesEvent, NavigatorSearchEvent, NavigatorSearchOpenComposer, NavigatorSearchSaveComposer, NavigatorSettingsEvent, NavigatorSettingsSaveComposer, NewConsoleMessageEvent, NewFriendRequestEvent, NewUserExperienceGetGiftsComposer, NewUserExperienceGiftOfferMessageEvent, NewUserExperienceNotCompleteEvent, NewUserExperienceScriptProceedComposer, NoobnessLevelMessageEvent, NoOwnedRoomsAlertMessageEvent, NoSuchFlatEvent, NotEnoughBalanceMessageEvent, NotificationDialogMessageEvent, NowPlayingMessageEvent, ObjectsDataUpdateEvent, ObjectsRollingEvent, OfferRewardDeliveredMessageEvent, OfficialSongIdMessageEvent, OneWayDoorStatusMessageEvent, OpenCampaignCalendarDoorAsStaffComposer, OpenCampaignCalendarDoorComposer, OpenMessageComposer, OpenMysteryTrophyMessageComposer, OpenPetPackageMessageComposer, OpenPetPackageRequestedMessageEvent, OpenPetPackageResultMessageEvent, OpenPresentComposer, OpenQuestTrackerMessageComposer, OpenWelcomeGiftComposer, OutgoingHeader, PeerUsersClassificationMessageComposer, PerformanceLogMessageComposer, PerkAllowancesMessageEvent, PetAddedToInventoryEvent, PetBreedingResultEvent, PetExperienceEvent, PetFigureUpdateEvent, PetInfoEvent, PetInventoryEvent, PetLevelNotificationEvent, PetLevelUpdateMessageEvent, PetMountComposer, PetMoveComposer, PetPlaceComposer, PetPlacingErrorEvent, PetReceivedMessageEvent, PetRemoveComposer, PetRemovedFromInventory, PetRespectComposer, PetRespectNoficationEvent, PetScratchFailedMessageEvent, PetSelectedMessageComposer, PetStatusUpdateEvent, PetSupplementComposer, PetSupplementedNotificationEvent, PetTrainingPanelMessageEvent, PhoneCollectionStateMessageEvent, PhotoCompetitionMessageComposer, PickIssuesMessageComposer, PlayListMessageEvent, PlayListSongAddedMessageEvent, PollAnswerComposer, PollContentsEvent, PollErrorEvent, PollOfferEvent, PollRejectComposer, PollStartComposer, PongMessageComposer, PopularRoomsSearchMessageComposer, PopularRoomTagsResultEvent, PostMessageMessageComposer, PostMessageMessageEvent, PostQuizAnswersComposer, PostThreadMessageEvent, PresentOpenedMessageEvent, ProductOfferEvent, PromoArticlesMessageEvent, PublishPhotoMessageComposer, PurchaseBasicMembershipExtensionComposer, PurchaseErrorMessageEvent, PurchaseFromCatalogAsGiftComposer, PurchaseFromCatalogComposer, PurchaseNotAllowedMessageEvent, PurchaseOKMessageEvent, PurchasePhotoMessageComposer, PurchaseRoomAdMessageComposer, PurchaseTargetedOfferComposer, PurchaseVipMembershipExtensionComposer, QuestCancelledMessageEvent, QuestCompletedMessageEvent, QuestDailyMessageEvent, QuestionAnsweredEvent, QuestionEvent, QuestionFinishedEvent, QuestMessageEvent, QuestsMessageEvent, QuizDataMessageEvent, QuizResultsMessageEvent, RateFlatMessageComposer, RecycleItemsMessageComposer, RecyclerFinishedMessageEvent, RecyclerStatusMessageEvent, RedeemCommunityGoalPrizeMessageComposer, RedeemItemClothingComposer, RedeemMarketplaceOfferCreditsMessageComposer, RedeemVoucherMessageComposer, RejectQuestMessageComposer, RelationshipStatusInfoEvent, ReleaseIssuesMessageComposer, RemainingMuteEvent, RemoveAllRightsMessageComposer, RemoveFriendComposer, RemoveJukeboxDiskComposer, RemoveOwnRoomRightsRoomMessageComposer, RemovePetSaddleComposer, RemoveWallItemComposer, RenderRoomMessageComposer, RenderRoomThumbnailMessageComposer, RentableSpaceCancelRentMessageComposer, RentableSpaceRentFailedMessageEvent, RentableSpaceRentMessageComposer, RentableSpaceRentOkMessageEvent, RentableSpaceStatusMessageComposer, RentableSpaceStatusMessageEvent, RequestABadgeComposer, RequestAchievementsMessageComposer, RequestBadgesComposer, RequestBotCommandConfigurationComposer, RequestCameraConfigurationComposer, RequestFriendComposer, RequestFurniInventoryWhenNotInRoomComposer, RequestPetInfoComposer, RequestPetsComposer, RequestSpamWallPostItMessageEvent, ResetPhoneNumberStateMessageComposer, ResetResolutionAchievementMessageComposer, RespectReceivedEvent, RestoreClientMessageEvent, RoomAdErrorEvent, RoomAdEventTabAdClickedComposer, RoomAdEventTabViewedComposer, RoomAdPurchaseInfoEvent, RoomAdPurchaseInitiatedComposer, RoomAdSearchMessageComposer, RoomAmbassadorAlertComposer, RoomBannedUsersComposer, RoomBanUserComposer, RoomChatlogEvent, RoomChatSettingsEvent, RoomCompetitionInitMessageComposer, RoomDeleteComposer, RoomDimmerPresetsEvent, RoomDoorbellAcceptedEvent, RoomDoorbellAccessComposer, RoomEnterComposer, RoomEnterErrorEvent, RoomEnterEvent, RoomEntryInfoMessageEvent, RoomEntryTileMessageEvent, RoomEventCancelEvent, RoomEventEvent, RoomFilterSettingsMessageEvent, RoomForwardEvent, RoomGiveRightsComposer, RoomHeightMapEvent, RoomHeightMapUpdateEvent, RoomInviteErrorEvent, RoomInviteEvent, RoomKickUserComposer, RoomMessageNotificationMessageEvent, RoomMuteComposer, RoomMuteUserComposer, RoomNetworkOpenConnectionMessageComposer, RoomOccupiedTilesMessageEvent, RoomPaintEvent, RoomPollResultEvent, RoomReadyMessageEvent, RoomRightsClearEvent, RoomRightsEvent, RoomRightsOwnerEvent, RoomScoreEvent, RoomSettingsComposer, RoomSettingsDataEvent, RoomSettingsErrorEvent, RoomSettingsSavedEvent, RoomSettingsSaveErrorEvent, RoomSettingsUpdatedEvent, RoomsWhereMyFriendsAreSearchMessageComposer, RoomsWithHighestScoreSearchMessageComposer, RoomTakeRightsComposer, RoomTextSearchMessageComposer, RoomThumbnailUpdateResultEvent, RoomUnbanUserComposer, RoomUnitActionComposer, RoomUnitBackgroundComposer, RoomUnitChatComposer, RoomUnitChatEvent, RoomUnitChatShoutComposer, RoomUnitChatShoutEvent, RoomUnitChatStyleComposer, RoomUnitChatWhisperComposer, RoomUnitChatWhisperEvent, RoomUnitDanceComposer, RoomUnitDanceEvent, RoomUnitDropHandItemComposer, RoomUnitEffectEvent, RoomUnitEvent, RoomUnitExpressionEvent, RoomUnitGiveHandItemComposer, RoomUnitGiveHandItemPetComposer, RoomUnitHandItemEvent, RoomUnitHandItemReceivedEvent, RoomUnitIdleEvent, RoomUnitInfoEvent, RoomUnitLookComposer, RoomUnitNumberEvent, RoomUnitPostureComposer, RoomUnitRemoveEvent, RoomUnitSignComposer, RoomUnitStatusEvent, RoomUnitTypingEvent, RoomUnitTypingStartComposer, RoomUnitTypingStopComposer, RoomUnitWalkComposer, RoomUsersClassificationMessageComposer, RoomUsersWithRightsComposer, RoomVisitsEvent, RoomVisualizationSettingsEvent, SanctionStatusEvent, SaveRoomSettingsComposer, SaveWardrobeOutfitMessageComposer, ScrGetKickbackInfoMessageComposer, ScrSendKickbackInfoMessageEvent, SearchFaqsMessageComposer, SeasonalCalendarDailyOfferMessageEvent, SeasonalQuestsMessageEvent, SecondsUntilMessageEvent, SelectClubGiftComposer, SellablePetPalettesMessageEvent, SendMessageComposer, SendRoomInviteComposer, SetActivatedBadgesComposer, SetClothingChangeDataMessageComposer, SetItemDataMessageComposer, SetObjectDataMessageComposer, SetPhoneNumberVerificationStatusMessageComposer, SetRelationshipStatusComposer, SetRoomSessionTagsMessageComposer, SetTargetedOfferStateComposer, SetYoutubeDisplayPlaylistMessageComposer, ShopTargetedOfferViewedComposer, ShowEnforceRoomCategoryDialogEvent, ShowMysteryBoxWaitMessageEvent, SimpleAlertMessageEvent, SSOTicketMessageComposer, StartCampaignMessageComposer, StartRoomPollEvent, SubmitRoomToCompetitionMessageComposer, TalentLevelUpEvent, TalentTrackComposer, TalentTrackLevelMessageEvent, TalentTrackMessageEvent, TargetedOfferEvent, TargetedOfferNotFoundEvent, ThreadMessagesMessageEvent, ThumbnailStatusMessageEvent, TogglePetBreedingComposer, TogglePetRidingComposer, ToggleStaffPickMessageComposer, TradingAcceptComposer, TradingAcceptEvent, TradingCancelComposer, TradingCloseComposer, TradingCloseEvent, TradingCompletedEvent, TradingConfirmationComposer, TradingConfirmationEvent, TradingListAddItemComposer, TradingListAddItemsComposer, TradingListItemEvent, TradingListItemRemoveComposer, TradingNoSuchItemEvent, TradingNotOpenEvent, TradingOpenComposer, TradingOpenEvent, TradingOpenFailedEvent, TradingOtherNotAllowedEvent, TradingUnacceptComposer, TradingYouAreNotAllowedEvent, TraxSongInfoMessageEvent, TryPhoneNumberMessageComposer, TryPhoneNumberResultMessageEvent, TryVerificationCodeResultMessageEvent, UnblockGroupMemberMessageComposer, UnignoreUserComposer, UniqueIDMessageComposer, UnloadGameMessageEvent, UnreadForumsCountMessageEvent, UnseenItemsEvent, UnseenResetCategoryComposer, UnseenResetItemsComposer, UpdateActionMessageComposer, UpdateConditionMessageComposer, UpdateFloorPropertiesMessageComposer, UpdateForumReadMarkerMessageComposer, UpdateForumSettingsMessageComposer, UpdateFurniturePositionComposer, UpdateHomeRoomMessageComposer, UpdateMessageMessageEvent, UpdateRoomCategoryAndTradeSettingsComposer, UpdateRoomFilterMessageComposer, UpdateRoomThumbnailMessageComposer, UpdateThreadMessageComposer, UpdateThreadMessageEvent, UpdateTriggerMessageComposer, UsePetProductComposer, UserBannedMessageEvent, UserChatlogEvent, UserClassificationMessageEvent, UserCreditsEvent, UserCurrencyComposer, UserCurrencyEvent, UserCurrentBadgesComposer, UserCurrentBadgesEvent, UserEventCatsEvent, UserFigureComposer, UserFlatCatsEvent, UserGameAchievementsMessageEvent, UserInfoEvent, UserMottoComposer, UserNameChangeMessageEvent, UserPermissionsEvent, UserPermissionsMapEvent, UserProfileComposer, UserProfileEvent, UserRelationshipsComposer, UserRespectComposer, UserSettingsCameraFollowComposer, UserSettingsEvent, UserSettingsOldChatComposer, UserSettingsRoomInvitesComposer, UserSettingsSoundComposer, UserSongDisksInventoryMessageEvent, UserSubscriptionComposer, UserSubscriptionEvent, UserTagsMessageEvent, UserUnbannedFromRoomEvent, UserWardrobePageEvent, VerifyCodeMessageComposer, VersionCheckMessageComposer, VisitUserComposer, VoteForRoomMessageComposer, VotePollCounterMessageComposer, VoucherRedeemErrorMessageEvent, VoucherRedeemOkMessageEvent, WardrobeMessageEvent, WeeklyCompetitiveFriendsLeaderboardEvent, WeeklyCompetitiveLeaderboardEvent, WeeklyGameRewardEvent, WeeklyGameRewardWinnersEvent, WelcomeGiftChangeEmailComposer, WelcomeGiftChangeEmailResultEvent, WelcomeGiftStatusEvent, WiredFurniActionEvent, WiredFurniConditionEvent, WiredFurniTriggerEvent, WiredOpenEvent, WiredRewardResultMessageEvent, WiredSaveSuccessEvent, WiredValidationErrorEvent, YouArePlayingGameEvent, YouAreSpectatorMessageEvent, YoutubeControlVideoMessageEvent, YoutubeDisplayPlaylistsEvent, YoutubeDisplayVideoMessageEvent, ClickUserMessageComposer } from './messages'; +import { AcceptFriendMessageComposer, AcceptFriendResultEvent, AcceptGameInviteMessageComposer, AcceptQuestMessageComposer, AccountSafetyLockStatusChangeMessageEvent, AchievementEvent, AchievementNotificationMessageEvent, AchievementResolutionCompletedMessageEvent, AchievementResolutionProgressMessageEvent, AchievementResolutionsMessageEvent, AchievementsEvent, AchievementsScoreEvent, ActivateQuestMessageComposer, ActivityPointNotificationMessageEvent, AddFavouriteRoomMessageComposer, AddJukeboxDiskComposer, AddSpamWallPostItMessageComposer, ApplySnapshotMessageComposer, ApplyTonerComposer, ApproveAllMembershipRequestsMessageComposer, ApproveNameMessageComposer, ApproveNameMessageEvent, AreaHideMessageEvent, AuthenticatedEvent, AuthenticationMessageComposer, AvailabilityStatusMessageEvent, AvailabilityTimeMessageEvent, AvailableCommandsEvent, AvatarEffectActivatedComposer, AvatarEffectActivatedEvent, AvatarEffectAddedEvent, AvatarEffectExpiredEvent, AvatarEffectSelectedComposer, AvatarEffectSelectedEvent, AvatarEffectsEvent, BadgePointLimitsEvent, BadgeReceivedEvent, BadgesEvent, BannedUsersFromRoomEvent, BonusRareInfoMessageEvent, BotAddedToInventoryEvent, BotCommandConfigurationEvent, BotErrorEvent, BotForceOpenContextMenuEvent, BotInventoryMessageEvent, BotPlaceComposer, BotReceivedMessageEvent, BotRemoveComposer, BotRemovedFromInventoryEvent, BotSkillListUpdateEvent, BotSkillSaveComposer, BreedPetsMessageComposer, BuildersClubFurniCountMessageEvent, BuildersClubPlaceRoomItemMessageComposer, BuildersClubPlaceWallItemMessageComposer, BuildersClubQueryFurniCountMessageComposer, BuildersClubSubscriptionStatusMessageEvent, BundleDiscountRulesetMessageEvent, BuyMarketplaceOfferMessageComposer, BuyMarketplaceTokensMessageComposer, CallForHelpDisabledNotifyMessageEvent, CallForHelpFromForumMessageMessageComposer, CallForHelpFromForumThreadMessageComposer, CallForHelpFromIMMessageComposer, CallForHelpFromPhotoMessageComposer, CallForHelpFromSelfieMessageComposer, CallForHelpMessageComposer, CallForHelpPendingCallsDeletedMessageEvent, CallForHelpPendingCallsMessageEvent, CallForHelpReplyMessageEvent, CallForHelpResultMessageEvent, CameraPublishStatusMessageEvent, CameraPurchaseOKMessageEvent, CameraSnapshotMessageEvent, CameraStorageUrlMessageEvent, CampaignCalendarDataMessageEvent, CampaignCalendarDoorOpenedMessageEvent, CancelEventMessageComposer, CancelMarketplaceOfferMessageComposer, CancelMysteryBoxWaitMessageEvent, CancelPetBreedingComposer, CancelQuestMessageComposer, CanCreateRoomEvent, CanCreateRoomEventEvent, CanCreateRoomMessageComposer, CatalogAdminCreateOfferComposer, CatalogAdminCreatePageComposer, CatalogAdminDeleteOfferComposer, CatalogAdminDeletePageComposer, CatalogAdminMoveOfferComposer, CatalogAdminMovePageComposer, CatalogAdminPublishComposer, CatalogAdminResultEvent, CatalogAdminSaveOfferComposer, CatalogAdminSavePageComposer, CatalogGroupsComposer, CatalogPageExpirationEvent, CatalogPageMessageEvent, CatalogPagesListEvent, CatalogPageWithEarliestExpiryMessageEvent, CatalogPublishedMessageEvent, CategoriesWithVisitorCountEvent, CfhChatlogEvent, CfhSanctionMessageEvent, CfhTopicsInitEvent, ChangeEmailComposer, ChangeEmailResultEvent, ChangeQueueMessageComposer, ChangeUserNameMessageComposer, ChangeUserNameResultMessageEvent, ChatReviewGuideDecidesOnOfferMessageComposer, ChatReviewGuideDetachedMessageComposer, ChatReviewGuideVoteMessageComposer, ChatReviewSessionCreateMessageComposer, ChatReviewSessionDetachedMessageEvent, ChatReviewSessionOfferedToGuideMessageEvent, ChatReviewSessionResultsMessageEvent, ChatReviewSessionStartedMessageEvent, ChatReviewSessionVotingStatusMessageEvent, CheckUserNameMessageComposer, CheckUserNameResultMessageEvent, ClickFurniMessageComposer, ClientHelloMessageComposer, ClientPingEvent, CloseIssueDefaultActionMessageComposer, CloseIssuesMessageComposer, ClubGiftInfoEvent, ClubGiftNotificationEvent, ClubGiftSelectedEvent, CommunityGoalEarnedPrizesMessageEvent, CommunityGoalHallOfFameMessageEvent, CommunityGoalProgressMessageEvent, CommunityGoalVoteMessageComposer, CommunityGoalVoteMessageEvent, CompetitionEntrySubmitResultEvent, CompetitionRoomsDataMessageEvent, CompetitionRoomsSearchMessageComposer, CompetitionStatusMessageEvent, CompetitionVotingInfoMessageEvent, CompleteDiffieHandshakeEvent, CompleteDiffieHandshakeMessageComposer, CompostPlantMessageComposer, ConcurrentUsersGoalProgressMessageEvent, ConfirmPetBreedingComposer, ConnectionErrorEvent, ControlYoutubeDisplayPlaybackMessageComposer, ConvertedRoomIdEvent, ConvertGlobalRoomIdMessageComposer, CraftableProductsEvent, CraftComposer, CraftingRecipeEvent, CraftingRecipesAvailableEvent, CraftingResultEvent, CraftSecretComposer, CreateFlatMessageComposer, CurrentTimingCodeMessageEvent, CustomUserNotificationMessageEvent, DeclineFriendMessageComposer, DefaultSanctionMessageComposer, DeleteBadgeMessageComposer, DeleteFavouriteRoomMessageComposer, DeleteItemMessageComposer, DeletePendingCallsForHelpMessageComposer, DeletePetMessageComposer, DesktopViewComposer, DesktopViewEvent, DiceValueMessageEvent, DirectSMSClubBuyAvailableMessageEvent, DisconnectMessageComposer, DisconnectReasonEvent, DoorbellMessageEvent, EditEventMessageComposer, ElementPointerMessageEvent, EmailStatusResultEvent, EpicPopupMessageEvent, ExtendedProfileChangedMessageEvent, ExtendRentOrBuyoutFurniMessageComposer, ExtendRentOrBuyoutStripItemMessageComposer, FavoriteMembershipUpdateMessageEvent, FavouriteChangedEvent, FavouritesEvent, FigureSetIdsMessageEvent, FigureUpdateEvent, FindFriendsProcessResultEvent, FindNewFriendsMessageComposer, FireworkChargeDataEvent, FlatAccessDeniedMessageEvent, FlatControllerAddedEvent, FlatControllerRemovedEvent, FlatControllersEvent, FlatCreatedEvent, FloodControlEvent, FloorHeightMapEvent, FollowFriendFailedEvent, FollowFriendMessageComposer, ForumDataMessageEvent, ForumsListMessageEvent, ForwardToACompetitionRoomMessageComposer, ForwardToARandomPromotedRoomMessageComposer, ForwardToASubmittableRoomMessageComposer, ForwardToRandomCompetitionRoomMessageComposer, ForwardToSomeRoomMessageComposer, FriendFurniConfirmLockMessageComposer, FriendListFragmentEvent, FriendListUpdateComposer, FriendListUpdateEvent, FriendNotificationEvent, FriendRequestQuestCompleteMessageComposer, FriendRequestsEvent, FurniRentOrBuyoutOfferMessageEvent, FurnitureAliasesComposer, FurnitureAliasesEvent, FurnitureColorWheelComposer, FurnitureDataEvent, FurnitureDiceActivateComposer, FurnitureDiceDeactivateComposer, FurnitureExchangeComposer, FurnitureFloorAddEvent, FurnitureFloorEvent, FurnitureFloorRemoveEvent, FurnitureFloorUpdateComposer, FurnitureFloorUpdateEvent, FurnitureGroupInfoComposer, FurnitureListAddOrUpdateEvent, FurnitureListComposer, FurnitureListEvent, FurnitureListInvalidateEvent, FurnitureListRemovedEvent, FurnitureMannequinSaveLookComposer, FurnitureMannequinSaveNameComposer, FurnitureMultiStateComposer, FurnitureOneWayDoorComposer, FurniturePickupAllComposer, FurniturePickupComposer, FurniturePlaceComposer, FurniturePlacePaintComposer, FurniturePostItPlaceComposer, FurniturePostItPlacedEvent, FurnitureRandomStateComposer, FurnitureStackHeightComposer, FurnitureStackHeightEvent, FurnitureWallAddEvent, FurnitureWallEvent, FurnitureWallMultiStateComposer, FurnitureWallRemoveEvent, FurnitureWallUpdateComposer, FurnitureWallUpdateEvent, FurniEditorBySpriteComposer, FurniEditorDeleteComposer, FurniEditorDetailComposer, FurniEditorDetailResultEvent, FurniEditorInteractionsComposer, FurniEditorInteractionsResultEvent, FurniEditorResultEvent, FurniEditorSearchComposer, FurniEditorSearchResultEvent, FurniEditorUpdateComposer, Game2AccountGameStatusMessageEvent, Game2CheckGameDirectoryStatusMessageComposer, Game2ExitGameMessageComposer, Game2GameChatMessageComposer, Game2GameDirectoryStatusMessageEvent, Game2GetAccountGameStatusMessageComposer, Game2GetWeeklyFriendsLeaderboardComposer, Game2GetWeeklyLeaderboardComposer, Game2InArenaQueueMessageEvent, Game2JoiningGameFailedMessageEvent, Game2LoadStageReadyMessageComposer, Game2PlayAgainMessageComposer, Game2RequestFullStatusUpdateMessageComposer, Game2StartingGameFailedMessageEvent, Game2StopCounterMessageEvent, Game2UserLeftGameMessageEvent, Game2WeeklyFriendsLeaderboardEvent, Game2WeeklyLeaderboardEvent, GameAchievementsMessageEvent, GameInviteMessageEvent, GameListMessageEvent, GameStatusMessageEvent, GameUnloadedMessageComposer, GenericErrorEvent, GetBadgePointLimitsComposer, GetBonusRareInfoMessageComposer, GetBotInventoryComposer, GetBundleDiscountRulesetComposer, GetCatalogIndexComposer, GetCatalogPageComposer, GetCatalogPageExpirationComposer, GetCatalogPageWithEarliestExpiryComposer, GetCategoriesWithUserCountMessageComposer, GetCfhChatlogMessageComposer, GetCfhStatusMessageComposer, GetClubGiftInfo, GetClubOffersMessageComposer, GetCommunityGoalEarnedPrizesMessageComposer, GetCommunityGoalHallOfFameMessageComposer, GetCommunityGoalProgressMessageComposer, GetConcurrentUsersGoalProgressMessageComposer, GetConcurrentUsersRewardMessageComposer, GetCraftableProductsComposer, GetCraftingRecipeComposer, GetCraftingRecipesAvailableComposer, GetCurrentTimingCodeMessageComposer, GetCustomRoomFilterMessageComposer, GetDailyQuestMessageComposer, GetDirectClubBuyAvailableComposer, GetEmailStatusComposer, GetExtendedProfileByNameMessageComposer, GetFaqCategoryMessageComposer, GetFaqTextMessageComposer, GetForumsListMessageComposer, GetForumStatsMessageComposer, GetFriendRequestsComposer, GetGameAchievementsMessageComposer, GetGameListMessageComposer, GetGameStatusMessageComposer, GetGiftMessageComposer, GetGiftWrappingConfigurationComposer, GetGuestRoomMessageComposer, GetGuestRoomResultEvent, GetGuideReportingStatusMessageComposer, GetHabboBasicMembershipExtendOfferComposer, GetHabboClubExtendOfferMessageComposer, GetHabboGroupBadgesMessageComposer, GetIgnoredUsersComposer, GetInterstitialMessageComposer, GetIsBadgeRequestFulfilledComposer, GetIsOfferGiftableComposer, GetIsUserPartOfCompetitionMessageComposer, GetItemDataComposer, GetJukeboxPlayListMessageComposer, GetLimitedOfferAppearingNextComposer, GetMarketplaceCanMakeOfferComposer, GetMarketplaceConfigurationMessageComposer, GetMarketplaceItemStatsComposer, GetMarketplaceOffersMessageComposer, GetMarketplaceOwnOffersMessageComposer, GetMessagesMessageComposer, GetModeratorRoomInfoMessageComposer, GetModeratorUserInfoMessageComposer, GetNextTargetedOfferComposer, GetNowPlayingMessageComposer, GetOccupiedTilesMessageComposer, GetOfficialRoomsMessageComposer, GetOfficialSongIdMessageComposer, GetPendingCallsForHelpMessageComposer, GetPetCommandsComposer, GetPopularRoomTagsMessageComposer, GetProductOfferComposer, GetPromoArticlesComposer, GetQuestsMessageComposer, GetQuizQuestionsComposer, GetRecyclerStatusMessageComposer, GetRentOrBuyoutOfferMessageComposer, GetResolutionAchievementsMessageComposer, GetRoomAdPurchaseInfoComposer, GetRoomChatlogMessageComposer, GetRoomEntryDataMessageComposer, GetRoomEntryTileMessageComposer, GetRoomVisitsMessageComposer, GetSeasonalCalendarDailyOfferComposer, GetSeasonalQuestsOnlyMessageComposer, GetSecondsUntilMessageComposer, GetSellablePetPalettesComposer, GetSongInfoMessageComposer, GetSoundMachinePlayListMessageComposer, GetSoundSettingsComposer, GetTalentTrackLevelMessageComposer, GetTargetedOfferComposer, GetThreadMessageComposer, GetThreadsMessageComposer, GetUnreadForumsCountMessageComposer, GetUserChatlogMessageComposer, GetUserEventCatsMessageComposer, GetUserFlatCatsMessageComposer, GetUserGameAchievementsMessageComposer, GetUserSongDisksMessageComposer, GetUserTagsComposer, GetWardrobeMessageComposer, GetWeeklyGameRewardComposer, GetWeeklyGameRewardWinnersComposer, GetYoutubeDisplayStatusMessageComposer, GiftReceiverNotFoundEvent, GiftWrappingConfigurationEvent, GotMysteryBoxPrizeMessageEvent, GoToFlatMessageComposer, GroupAdminGiveComposer, GroupAdminTakeComposer, GroupBadgePartsComposer, GroupBadgePartsEvent, GroupBuyComposer, GroupBuyDataComposer, GroupBuyDataEvent, GroupConfirmMemberRemoveEvent, GroupConfirmRemoveMemberComposer, GroupDeleteComposer, GroupDetailsChangedMessageEvent, GroupFavoriteComposer, GroupFurniContextMenuInfoMessageEvent, GroupInformationComposer, GroupInformationEvent, GroupJoinComposer, GroupMembersComposer, GroupMembersEvent, GroupMembershipAcceptComposer, GroupMembershipDeclineComposer, GroupMembershipRequestedMessageEvent, GroupPurchasedEvent, GroupRemoveMemberComposer, GroupSaveBadgeComposer, GroupSaveColorsComposer, GroupSaveInformationComposer, GroupSavePreferencesComposer, GroupSettingsComposer, GroupSettingsEvent, GroupUnfavoriteComposer, GuestRoomSearchResultEvent, GuideOnDutyStatusMessageEvent, GuideReportingStatusMessageEvent, GuideSessionAttachedMessageEvent, GuideSessionCreateMessageComposer, GuideSessionDetachedMessageEvent, GuideSessionEndedMessageEvent, GuideSessionErrorMessageEvent, GuideSessionFeedbackMessageComposer, GuideSessionGetRequesterRoomMessageComposer, GuideSessionGuideDecidesMessageComposer, GuideSessionInvitedToGuideRoomMessageEvent, GuideSessionInviteRequesterMessageComposer, GuideSessionIsTypingMessageComposer, GuideSessionMessageMessageComposer, GuideSessionMessageMessageEvent, GuideSessionOnDutyUpdateMessageComposer, GuideSessionPartnerIsTypingMessageEvent, GuideSessionReportMessageComposer, GuideSessionRequesterCancelsMessageComposer, GuideSessionRequesterRoomMessageEvent, GuideSessionResolvedMessageComposer, GuideSessionStartedMessageEvent, GuideTicketCreationResultMessageEvent, GuideTicketResolutionMessageEvent, GuildBaseSearchMessageComposer, GuildEditFailedMessageEvent, GuildForumThreadsEvent, GuildMemberMgmtFailedMessageEvent, GuildMembershipsMessageEvent, HabboBroadcastMessageEvent, HabboClubExtendOfferMessageEvent, HabboClubOffersMessageEvent, HabboGroupBadgesMessageEvent, HabboGroupDeactivatedMessageEvent, HabboGroupJoinFailedMessageEvent, HabboSearchComposer, HabboSearchResultEvent, HarvestPetMessageComposer, HotelClosedAndOpensEvent, HotelClosesAndWillOpenAtEvent, HotelMergeNameChangeEvent, HotelWillCloseInMinutesEvent, IdentityAccountsEvent, IgnoredUsersEvent, IgnoreResultEvent, IgnoreUserComposer, IgnoreUserIdComposer, InClientLinkEvent, IncomingHeader, InfoFeedEnableMessageEvent, InfoRetrieveMessageComposer, InitCameraMessageEvent, InitDiffieHandshakeEvent, InitDiffieHandshakeMessageComposer, InstantMessageErrorEvent, InterstitialMessageEvent, InterstitialShownMessageComposer, IsBadgeRequestFulfilledEvent, IsOfferGiftableMessageEvent, IssueCloseNotificationMessageEvent, IssueDeletedMessageEvent, IssueInfoMessageEvent, IssuePickFailedMessageEvent, IsUserPartOfCompetitionMessageEvent, ItemDataUpdateMessageEvent, JoinedQueueMessageEvent, JoiningQueueFailedMessageEvent, JoinQueueMessageComposer, JukeboxPlayListFullMessageEvent, JukeboxSongDisksMessageEvent, LagWarningReportMessageComposer, LeaveQueueMessageComposer, LeftQueueMessageEvent, LimitedEditionSoldOutEvent, LimitedOfferAppearingNextMessageEvent, LoadGameMessageEvent, LoadGameUrlEvent, LoveLockFurniFinishedEvent, LoveLockFurniFriendConfirmedEvent, LoveLockFurniStartEvent, MaintenanceStatusMessageEvent, MakeOfferMessageComposer, MarkCatalogNewAdditionsPageOpenedComposer, MarketplaceBuyOfferResultEvent, MarketplaceCancelOfferResultEvent, MarketplaceCanMakeOfferResult, MarketplaceConfigurationEvent, MarketplaceItemStatsEvent, MarketplaceMakeOfferResult, MarketPlaceOffersEvent, MarketplaceOwnOffersEvent, MessageErrorEvent, MessengerInitComposer, MessengerInitEvent, MiniMailNewMessageEvent, MiniMailUnreadCountEvent, ModAlertMessageComposer, ModBanMessageComposer, ModerateMessageMessageComposer, ModerateRoomMessageComposer, ModerateThreadMessageComposer, ModeratorActionMessageComposer, ModeratorActionResultMessageEvent, ModeratorCautionEvent, ModeratorInitMessageEvent, ModeratorMessageEvent, ModeratorRoomInfoEvent, ModeratorToolPreferencesEvent, ModeratorUserInfoEvent, ModKickMessageComposer, ModMessageMessageComposer, ModMuteMessageComposer, ModToolPreferencesComposer, ModToolSanctionComposer, ModTradingLockMessageComposer, MoodlightSettingsComposer, MoodlightSettingsSaveComposer, MoodlightTogggleStateComposer, MOTDNotificationEvent, MuteAllInRoomEvent, MyFavouriteRoomsSearchMessageComposer, MyFrequentRoomHistorySearchMessageComposer, MyFriendsRoomsSearchMessageComposer, MyGuildBasesSearchMessageComposer, MyRecommendedRoomsMessageComposer, MyRoomHistorySearchMessageComposer, MyRoomRightsSearchMessageComposer, MyRoomsSearchMessageComposer, MysteryBoxKeysEvent, MysteryBoxWaitingCanceledMessageComposer, NavigatorCategoryListModeComposer, NavigatorCollapsedEvent, NavigatorDeleteSavedSearchComposer, NavigatorHomeRoomEvent, NavigatorInitComposer, NavigatorLiftedEvent, NavigatorMetadataEvent, NavigatorOpenRoomCreatorEvent, NavigatorSearchCloseComposer, NavigatorSearchComposer, NavigatorSearchesEvent, NavigatorSearchEvent, NavigatorSearchOpenComposer, NavigatorSearchSaveComposer, NavigatorSettingsEvent, NavigatorSettingsSaveComposer, NewConsoleMessageEvent, NewFriendRequestEvent, NewUserExperienceGetGiftsComposer, NewUserExperienceGiftOfferMessageEvent, NewUserExperienceNotCompleteEvent, NewUserExperienceScriptProceedComposer, NoobnessLevelMessageEvent, NoOwnedRoomsAlertMessageEvent, NoSuchFlatEvent, NotEnoughBalanceMessageEvent, NotificationDialogMessageEvent, NowPlayingMessageEvent, ObjectsDataUpdateEvent, ObjectsRollingEvent, OfferRewardDeliveredMessageEvent, OfficialSongIdMessageEvent, OneWayDoorStatusMessageEvent, OpenCampaignCalendarDoorAsStaffComposer, OpenCampaignCalendarDoorComposer, OpenMessageComposer, OpenMysteryTrophyMessageComposer, OpenPetPackageMessageComposer, OpenPetPackageRequestedMessageEvent, OpenPetPackageResultMessageEvent, OpenPresentComposer, OpenQuestTrackerMessageComposer, OpenWelcomeGiftComposer, OutgoingHeader, PeerUsersClassificationMessageComposer, PerformanceLogMessageComposer, PerkAllowancesMessageEvent, PetAddedToInventoryEvent, PetBreedingResultEvent, PetExperienceEvent, PetFigureUpdateEvent, PetInfoEvent, PetInventoryEvent, PetLevelNotificationEvent, PetLevelUpdateMessageEvent, PetMountComposer, PetMoveComposer, PetPlaceComposer, PetPlacingErrorEvent, PetReceivedMessageEvent, PetRemoveComposer, PetRemovedFromInventory, PetRespectComposer, PetRespectNoficationEvent, PetScratchFailedMessageEvent, PetSelectedMessageComposer, PetStatusUpdateEvent, PetSupplementComposer, PetSupplementedNotificationEvent, PetTrainingPanelMessageEvent, PhoneCollectionStateMessageEvent, PhotoCompetitionMessageComposer, PickIssuesMessageComposer, PlayListMessageEvent, PlayListSongAddedMessageEvent, PollAnswerComposer, PollContentsEvent, PollErrorEvent, PollOfferEvent, PollRejectComposer, PollStartComposer, PongMessageComposer, PopularRoomsSearchMessageComposer, PopularRoomTagsResultEvent, PostMessageMessageComposer, PostMessageMessageEvent, PostQuizAnswersComposer, PostThreadMessageEvent, PresentOpenedMessageEvent, ProductOfferEvent, PromoArticlesMessageEvent, PublishPhotoMessageComposer, PurchaseBasicMembershipExtensionComposer, PurchaseErrorMessageEvent, PurchaseFromCatalogAsGiftComposer, PurchaseFromCatalogComposer, PurchaseNotAllowedMessageEvent, PurchaseOKMessageEvent, PurchasePhotoMessageComposer, PurchaseRoomAdMessageComposer, PurchaseTargetedOfferComposer, PurchaseVipMembershipExtensionComposer, QuestCancelledMessageEvent, QuestCompletedMessageEvent, QuestDailyMessageEvent, QuestionAnsweredEvent, QuestionEvent, QuestionFinishedEvent, QuestMessageEvent, QuestsMessageEvent, QuizDataMessageEvent, QuizResultsMessageEvent, RateFlatMessageComposer, RecycleItemsMessageComposer, RecyclerFinishedMessageEvent, RecyclerStatusMessageEvent, RedeemCommunityGoalPrizeMessageComposer, RedeemItemClothingComposer, RedeemMarketplaceOfferCreditsMessageComposer, RedeemVoucherMessageComposer, RejectQuestMessageComposer, RelationshipStatusInfoEvent, ReleaseIssuesMessageComposer, RemainingMuteEvent, RemoveAllRightsMessageComposer, RemoveFriendComposer, RemoveJukeboxDiskComposer, RemoveOwnRoomRightsRoomMessageComposer, RemovePetSaddleComposer, RemoveWallItemComposer, RenderRoomMessageComposer, RenderRoomThumbnailMessageComposer, RentableSpaceCancelRentMessageComposer, RentableSpaceRentFailedMessageEvent, RentableSpaceRentMessageComposer, RentableSpaceRentOkMessageEvent, RentableSpaceStatusMessageComposer, RentableSpaceStatusMessageEvent, RequestABadgeComposer, RequestAchievementsMessageComposer, RequestBadgesComposer, RequestBotCommandConfigurationComposer, RequestCameraConfigurationComposer, RequestFriendComposer, RequestFurniInventoryWhenNotInRoomComposer, RequestPetInfoComposer, RequestPetsComposer, RequestSpamWallPostItMessageEvent, ResetPhoneNumberStateMessageComposer, ResetResolutionAchievementMessageComposer, RespectReceivedEvent, RestoreClientMessageEvent, RoomAdErrorEvent, RoomAdEventTabAdClickedComposer, RoomAdEventTabViewedComposer, RoomAdPurchaseInfoEvent, RoomAdPurchaseInitiatedComposer, RoomAdSearchMessageComposer, RoomAmbassadorAlertComposer, RoomBannedUsersComposer, RoomBanUserComposer, RoomChatlogEvent, RoomChatSettingsEvent, RoomCompetitionInitMessageComposer, RoomDeleteComposer, RoomDimmerPresetsEvent, RoomDoorbellAcceptedEvent, RoomDoorbellAccessComposer, RoomEnterComposer, RoomEnterErrorEvent, RoomEnterEvent, RoomEntryInfoMessageEvent, RoomEntryTileMessageEvent, RoomEventCancelEvent, RoomEventEvent, RoomFilterSettingsMessageEvent, RoomForwardEvent, RoomGiveRightsComposer, RoomHeightMapEvent, RoomHeightMapUpdateEvent, RoomInviteErrorEvent, RoomInviteEvent, RoomKickUserComposer, RoomMessageNotificationMessageEvent, RoomMuteComposer, RoomMuteUserComposer, RoomNetworkOpenConnectionMessageComposer, RoomOccupiedTilesMessageEvent, RoomPaintEvent, RoomPollResultEvent, RoomReadyMessageEvent, RoomRightsClearEvent, RoomRightsEvent, RoomRightsOwnerEvent, RoomScoreEvent, RoomSettingsComposer, RoomSettingsDataEvent, RoomSettingsErrorEvent, RoomSettingsSavedEvent, RoomSettingsSaveErrorEvent, RoomSettingsUpdatedEvent, RoomsWhereMyFriendsAreSearchMessageComposer, RoomsWithHighestScoreSearchMessageComposer, RoomTakeRightsComposer, RoomTextSearchMessageComposer, RoomThumbnailUpdateResultEvent, RoomUnbanUserComposer, RoomUnitActionComposer, RoomUnitBackgroundComposer, RoomUnitChatComposer, RoomUnitChatEvent, RoomUnitChatShoutComposer, RoomUnitChatShoutEvent, RoomUnitChatStyleComposer, RoomUnitChatWhisperComposer, RoomUnitChatWhisperEvent, RoomUnitDanceComposer, RoomUnitDanceEvent, RoomUnitDropHandItemComposer, RoomUnitEffectEvent, RoomUnitEvent, RoomUnitExpressionEvent, RoomUnitGiveHandItemComposer, RoomUnitGiveHandItemPetComposer, RoomUnitHandItemEvent, RoomUnitHandItemReceivedEvent, RoomUnitIdleEvent, RoomUnitInfoEvent, RoomUnitLookComposer, RoomUnitNumberEvent, RoomUnitPostureComposer, RoomUnitRemoveEvent, RoomUnitSignComposer, RoomUnitStatusEvent, RoomUnitTypingEvent, RoomUnitTypingStartComposer, RoomUnitTypingStopComposer, RoomUnitWalkComposer, RoomUsersClassificationMessageComposer, RoomUsersWithRightsComposer, RoomVisitsEvent, RoomVisualizationSettingsEvent, SanctionStatusEvent, SaveRoomSettingsComposer, SaveWardrobeOutfitMessageComposer, ScrGetKickbackInfoMessageComposer, ScrSendKickbackInfoMessageEvent, SearchFaqsMessageComposer, SeasonalCalendarDailyOfferMessageEvent, SeasonalQuestsMessageEvent, SecondsUntilMessageEvent, SelectClubGiftComposer, SellablePetPalettesMessageEvent, SendMessageComposer, SendRoomInviteComposer, SetActivatedBadgesComposer, SetClothingChangeDataMessageComposer, SetItemDataMessageComposer, SetObjectDataMessageComposer, SetPhoneNumberVerificationStatusMessageComposer, SetRelationshipStatusComposer, SetRoomSessionTagsMessageComposer, SetTargetedOfferStateComposer, SetYoutubeDisplayPlaylistMessageComposer, ShopTargetedOfferViewedComposer, ShowEnforceRoomCategoryDialogEvent, ShowMysteryBoxWaitMessageEvent, SimpleAlertMessageEvent, SSOTicketMessageComposer, StartCampaignMessageComposer, StartRoomPollEvent, SubmitRoomToCompetitionMessageComposer, TalentLevelUpEvent, TalentTrackComposer, TalentTrackLevelMessageEvent, TalentTrackMessageEvent, TargetedOfferEvent, TargetedOfferNotFoundEvent, ThreadMessagesMessageEvent, ThumbnailStatusMessageEvent, TogglePetBreedingComposer, TogglePetRidingComposer, ToggleStaffPickMessageComposer, TradingAcceptComposer, TradingAcceptEvent, TradingCancelComposer, TradingCloseComposer, TradingCloseEvent, TradingCompletedEvent, TradingConfirmationComposer, TradingConfirmationEvent, TradingListAddItemComposer, TradingListAddItemsComposer, TradingListItemEvent, TradingListItemRemoveComposer, TradingNoSuchItemEvent, TradingNotOpenEvent, TradingOpenComposer, TradingOpenEvent, TradingOpenFailedEvent, TradingOtherNotAllowedEvent, TradingUnacceptComposer, TradingYouAreNotAllowedEvent, TraxSongInfoMessageEvent, TryPhoneNumberMessageComposer, TryPhoneNumberResultMessageEvent, TryVerificationCodeResultMessageEvent, UnblockGroupMemberMessageComposer, UnignoreUserComposer, UniqueIDMessageComposer, UnloadGameMessageEvent, UnreadForumsCountMessageEvent, UnseenItemsEvent, UnseenResetCategoryComposer, UnseenResetItemsComposer, UpdateActionMessageComposer, UpdateConditionMessageComposer, UpdateFloorPropertiesMessageComposer, UpdateForumReadMarkerMessageComposer, UpdateForumSettingsMessageComposer, UpdateFurniturePositionComposer, UpdateHomeRoomMessageComposer, UpdateMessageMessageEvent, UpdateRoomCategoryAndTradeSettingsComposer, UpdateRoomFilterMessageComposer, UpdateRoomThumbnailMessageComposer, UpdateThreadMessageComposer, UpdateThreadMessageEvent, UpdateTriggerMessageComposer, UsePetProductComposer, UserBannedMessageEvent, UserChatlogEvent, UserClassificationMessageEvent, UserCreditsEvent, UserCurrencyComposer, UserCurrencyEvent, UserCurrentBadgesComposer, UserCurrentBadgesEvent, UserEventCatsEvent, UserFigureComposer, UserFlatCatsEvent, UserGameAchievementsMessageEvent, UserInfoEvent, UserMottoComposer, UserNameChangeMessageEvent, UserPermissionsEvent, UserProfileComposer, UserProfileEvent, UserRelationshipsComposer, UserRespectComposer, UserSettingsCameraFollowComposer, UserSettingsEvent, UserSettingsOldChatComposer, UserSettingsRoomInvitesComposer, UserSettingsSoundComposer, UserSongDisksInventoryMessageEvent, UserSubscriptionComposer, UserSubscriptionEvent, UserTagsMessageEvent, UserUnbannedFromRoomEvent, UserWardrobePageEvent, VerifyCodeMessageComposer, VersionCheckMessageComposer, VisitUserComposer, VoteForRoomMessageComposer, VotePollCounterMessageComposer, VoucherRedeemErrorMessageEvent, VoucherRedeemOkMessageEvent, WardrobeMessageEvent, WeeklyCompetitiveFriendsLeaderboardEvent, WeeklyCompetitiveLeaderboardEvent, WeeklyGameRewardEvent, WeeklyGameRewardWinnersEvent, WelcomeGiftChangeEmailComposer, WelcomeGiftChangeEmailResultEvent, WelcomeGiftStatusEvent, WiredFurniActionEvent, WiredFurniConditionEvent, WiredFurniTriggerEvent, WiredOpenEvent, WiredRewardResultMessageEvent, WiredSaveSuccessEvent, WiredValidationErrorEvent, YouArePlayingGameEvent, YouAreSpectatorMessageEvent, YoutubeControlVideoMessageEvent, YoutubeDisplayPlaylistsEvent, YoutubeDisplayVideoMessageEvent, ClickUserMessageComposer } from './messages'; import { PurchaseCatalogPrefixComposer, PurchaseNickIconComposer, PurchasePrefixComposer, RequestNickIconsComposer, SetActiveNickIconComposer, SetActivePrefixComposer, SetDisplayOrderComposer, UserNickIconsEvent } from './messages'; import { WiredMonitorDataEvent, WiredMonitorRequestComposer, WiredRoomSettingsDataEvent, WiredRoomSettingsRequestComposer, WiredRoomSettingsSaveComposer, WiredUserInspectMoveComposer, WiredUserVariableManageComposer, WiredUserVariableUpdateComposer, WiredUserVariablesDataEvent, WiredUserVariablesRequestComposer } from './messages'; import { WiredMovementsEvent } from './messages'; @@ -533,7 +533,6 @@ export class NitroMessages implements IMessageConfiguration this._events.set(IncomingHeader.USER_IGNORED_RESULT, IgnoreResultEvent); this._events.set(IncomingHeader.USER_RESPECT, RespectReceivedEvent); this._events.set(IncomingHeader.USER_PERMISSIONS, UserPermissionsEvent); - this._events.set(IncomingHeader.USER_PERMISSIONS_MAP, UserPermissionsMapEvent); this._events.set(IncomingHeader.USER_BADGES_CURRENT, UserCurrentBadgesEvent); this._events.set(IncomingHeader.USER_INFO, UserInfoEvent); this._events.set(IncomingHeader.UNIT_CHANGE_NAME, UserNameChangeMessageEvent); diff --git a/packages/communication/src/messages/incoming/IncomingHeader.ts b/packages/communication/src/messages/incoming/IncomingHeader.ts index 6a5f7dd..70dbad3 100644 --- a/packages/communication/src/messages/incoming/IncomingHeader.ts +++ b/packages/communication/src/messages/incoming/IncomingHeader.ts @@ -255,8 +255,6 @@ export class IncomingHeader public static USER_OUTFITS = 3315; public static USER_PERKS = 2586; public static USER_PERMISSIONS = 411; - // Resolved permission map (Arcturus extension, Outgoing.UserPermissionsMapComposer = 10070). - public static USER_PERMISSIONS_MAP = 10070; public static USER_PET_ADD = 2101; public static USER_PET_REMOVE = 3253; public static USER_PETS = 3522; diff --git a/packages/communication/src/messages/incoming/user/access/UserPermissionsMapEvent.ts b/packages/communication/src/messages/incoming/user/access/UserPermissionsMapEvent.ts deleted file mode 100644 index b0653cb..0000000 --- a/packages/communication/src/messages/incoming/user/access/UserPermissionsMapEvent.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { IMessageEvent } from '@nitrots/api'; -import { MessageEvent } from '@nitrots/events'; -import { UserPermissionsMapParser } from '../../../parser'; - -export class UserPermissionsMapEvent extends MessageEvent implements IMessageEvent -{ - constructor(callBack: Function) - { - super(callBack, UserPermissionsMapParser); - } - - public getParser(): UserPermissionsMapParser - { - return this.parser as UserPermissionsMapParser; - } -} diff --git a/packages/communication/src/messages/incoming/user/access/index.ts b/packages/communication/src/messages/incoming/user/access/index.ts index 3d07ab1..51901c3 100644 --- a/packages/communication/src/messages/incoming/user/access/index.ts +++ b/packages/communication/src/messages/incoming/user/access/index.ts @@ -1,2 +1 @@ export * from './UserPermissionsEvent'; -export * from './UserPermissionsMapEvent'; diff --git a/packages/communication/src/messages/parser/user/access/UserPermissionsMapParser.ts b/packages/communication/src/messages/parser/user/access/UserPermissionsMapParser.ts deleted file mode 100644 index 2e23a8e..0000000 --- a/packages/communication/src/messages/parser/user/access/UserPermissionsMapParser.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { IMessageDataWrapper, IMessageParser } from '@nitrots/api'; - -/** - * Parses the resolved permission map for the current user - * (Arcturus-Morningstar-Extended ≥ 4.2.10, header - * `Outgoing.UserPermissionsMapComposer = 10070`). - * - * Wire layout: - * int count - * loop: string permission_key + int value (1 = ALLOWED, 2 = ROOM_OWNER) - * - * Only permissions whose `PermissionSetting != DISALLOWED` are sent — - * absence means "no". The renderer-side `SessionDataManager` consumes - * this and exposes it via a snapshot getter; React-side - * `useHasPermission(key)` drives UI gates against the real - * `permission_definitions.permission_key` strings instead of - * deployment-specific rank IDs. - */ -export class UserPermissionsMapParser implements IMessageParser -{ - private _permissions: Map = new Map(); - - public flush(): boolean - { - this._permissions = new Map(); - - return true; - } - - public parse(wrapper: IMessageDataWrapper): boolean - { - if(!wrapper) return false; - - const count = wrapper.readInt(); - const fresh = new Map(); - - for(let i = 0; i < count; i++) - { - const key = wrapper.readString(); - const value = wrapper.readInt(); - - fresh.set(key, value); - } - - this._permissions = fresh; - - return true; - } - - public get permissions(): ReadonlyMap - { - return this._permissions; - } -} diff --git a/packages/communication/src/messages/parser/user/access/UserPermissionsParser.ts b/packages/communication/src/messages/parser/user/access/UserPermissionsParser.ts index 20d5fb6..369ba41 100644 --- a/packages/communication/src/messages/parser/user/access/UserPermissionsParser.ts +++ b/packages/communication/src/messages/parser/user/access/UserPermissionsParser.ts @@ -10,6 +10,7 @@ export class UserPermissionsParser implements IMessageParser private _rankBadge: string; private _rankPrefix: string; private _rankPrefixColor: string; + private _permissions: Map = new Map(); public flush(): boolean { @@ -21,6 +22,7 @@ export class UserPermissionsParser implements IMessageParser this._rankBadge = ''; this._rankPrefix = ''; this._rankPrefixColor = ''; + this._permissions = new Map(); return true; } @@ -34,9 +36,9 @@ export class UserPermissionsParser implements IMessageParser this._isAmbassador = wrapper.readBoolean(); // Optional trailing block (Arcturus-Morningstar-Extended ≥ 4.2.10): - // rank metadata appended in a backward-compatible way. Older - // emulators don't write these bytes so we keep the defaults - // from flush(). + // rank metadata + resolved permission map appended in a + // backward-compatible way. Older emulators don't write these + // bytes so we keep the defaults from flush(). if(!wrapper.bytesAvailable) return true; this._rankId = wrapper.readInt(); @@ -45,6 +47,25 @@ export class UserPermissionsParser implements IMessageParser this._rankPrefix = wrapper.readString(); this._rankPrefixColor = wrapper.readString(); + if(!wrapper.bytesAvailable) return true; + + // Resolved permission map: int count + (string key, int value)*. + // value 1 = ALLOWED, 2 = ROOM_OWNER. Only entries with + // PermissionSetting != DISALLOWED are sent; absence on the client + // means "no" (useHasPermission(key) returns false). + const count = wrapper.readInt(); + const permissions = new Map(); + + for(let i = 0; i < count; i++) + { + const key = wrapper.readString(); + const value = wrapper.readInt(); + + permissions.set(key, value); + } + + this._permissions = permissions; + return true; } @@ -87,4 +108,9 @@ export class UserPermissionsParser implements IMessageParser { return this._rankPrefixColor; } + + public get permissions(): ReadonlyMap + { + return this._permissions; + } } diff --git a/packages/communication/src/messages/parser/user/access/index.ts b/packages/communication/src/messages/parser/user/access/index.ts index 43a3c84..7cb9b64 100644 --- a/packages/communication/src/messages/parser/user/access/index.ts +++ b/packages/communication/src/messages/parser/user/access/index.ts @@ -1,2 +1 @@ -export * from './UserPermissionsMapParser'; export * from './UserPermissionsParser'; diff --git a/packages/session/src/SessionDataManager.ts b/packages/session/src/SessionDataManager.ts index 213c2a1..64e62fa 100644 --- a/packages/session/src/SessionDataManager.ts +++ b/packages/session/src/SessionDataManager.ts @@ -1,5 +1,5 @@ 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, UserPermissionsMapEvent, UserRespectComposer, UserTagsMessageEvent } from '@nitrots/communication'; +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, NitroEvent, NitroEventType, NitroSettingsEvent, SessionDataPreferencesEvent, UserNameUpdateEvent } from '@nitrots/events'; @@ -161,7 +161,6 @@ export class SessionDataManager implements ISessionDataManager })), GetCommunication().registerMessageEvent(new UserInfoEvent(this.onUserInfoEvent.bind(this))), GetCommunication().registerMessageEvent(new UserPermissionsEvent(this.onUserPermissionsEvent.bind(this))), - GetCommunication().registerMessageEvent(new UserPermissionsMapEvent(this.onUserPermissionsMapEvent.bind(this))), GetCommunication().registerMessageEvent(new AvailabilityStatusMessageEvent(this.onAvailabilityStatusMessageEvent.bind(this))), GetCommunication().registerMessageEvent(new PetScratchFailedMessageEvent(this.onPetRespectFailed.bind(this))), GetCommunication().registerMessageEvent(new ChangeUserNameResultMessageEvent(this.onChangeNameUpdateEvent.bind(this))), @@ -293,18 +292,19 @@ export class SessionDataManager implements ISessionDataManager this._rankBadge = parser.rankBadge; this._rankPrefix = parser.rankPrefix; this._rankPrefixColor = parser.rankPrefixColor; + // Copy into our local mutable Map so the parser's reference + // (which is overwritten on every parse() call) can't leak back + // to consumers. + this._permissions = new Map(parser.permissions); + // Invalidate BOTH snapshots: a UserPermissionsComposer push from + // the emulator refreshes user-data fields (clubLevel/securityLevel + // /rank metadata) AND the resolved permission map. Keep the two + // invalidation events distinct so React consumers can subscribe + // to just one (e.g. a widget that only cares about + // useHasPermission re-renders only when the map actually + // changes, not on every snapshot bump). this.invalidateUserDataSnapshot(); - } - - private onUserPermissionsMapEvent(event: UserPermissionsMapEvent): void - { - if(!event || !event.connection) return; - - // Copy into our local mutable Map so the parser's reference (which - // is overwritten on every parse() call) can't leak back to consumers. - this._permissions = new Map(event.getParser().permissions); - this.invalidatePermissionsSnapshot(); } From cc1e8fe9c73b9269ec9155a865389a298bc749c8 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Tue, 19 May 2026 20:38:47 +0200 Subject: [PATCH 26/30] fix(api): align IRoomSession.sendBackgroundMessage signature with the impl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The RoomSession.sendBackgroundMessage impl takes 5 args (background, stand, overlay, card, border) but the interface only declared 4 — TypeScript consumers calling roomSession.sendBackgroundMessage(...) with the border arg failed to typecheck even though the runtime call worked. Add the optional backgroundBorder?: number trailing parameter to the interface so the contract matches what RoomSession.ts ships. --- packages/api/src/nitro/session/IRoomSession.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/nitro/session/IRoomSession.ts b/packages/api/src/nitro/session/IRoomSession.ts index cfd5f1f..5323a9b 100644 --- a/packages/api/src/nitro/session/IRoomSession.ts +++ b/packages/api/src/nitro/session/IRoomSession.ts @@ -13,7 +13,7 @@ export interface IRoomSession sendShoutMessage(text: string, styleId: number, chatColour?: string): void; sendWhisperMessage(recipientName: string, text: string, styleId: number): void; sendChatTypingMessage(isTyping: boolean): void; - sendBackgroundMessage(backgroundImage: number, backgroundStand: number, backgroundOverlay: number, backgroundCard?: number): void; + sendBackgroundMessage(backgroundImage: number, backgroundStand: number, backgroundOverlay: number, backgroundCard?: number, backgroundBorder?: number): void; sendMottoMessage(motto: string): void; sendDanceMessage(danceId: number): void; sendExpressionMessage(expression: number): void; From 05ea0db806c50a02f45ac8e9a2cbb8ad55dc72d9 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Tue, 19 May 2026 21:05:36 +0200 Subject: [PATCH 27/30] fix(parser): drop unsafe borderId read inside the RoomUnitParser per-user loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Infostand Borders merge (origin/Dev 4b7d04d, upstream commit) added user.borderId = (wrapper.bytesAvailable ? wrapper.readInt() : 0); inside the per-user loop in RoomUnitParser (the parser for the RoomUsersComposer packet — header 3920 — which ships the full roster on room enter). The guard is unsafe inside a loop: `bytesAvailable` is a boolean meaning "any bytes left in the WHOLE packet?", not "any bytes left in THIS user record". For every user except the last one, `bytesAvailable === true` because the NEXT user's bytes still follow, so the parser reads an int and steals 4 bytes from the next user — cascade corruption of the entire roster. Symptom in production: users don't see each other on first room sight. The roster arrives, the parser sfasa, RoomEngine drops the malformed records. Fix: stop reading borderId inside the loop. The per-user border id is shipped separately via RoomUnitInfoParser (single-user packet, no loop), where the bytesAvailable guard is safe. The roster packet's last-tail extension story stays clean for any future trailing block the same way other parsers do — but only when the guard is the LAST read in the packet, not a per-record one. This also makes the renderer wire-compatible with both old emulators (no borderId at all) and the new Arcturus version that ships borderId in RoomUsersComposer — the latter just has 4 extra trailing bytes per user that the parser ignores. A follow-up change on Arcturus' RoomUsersComposer can drop the borderId append, or keep it and the client simply doesn't read it from the roster (which is fine — the infostand re-fetch via RoomUnitInfoParser gives the authoritative border). mvn-equivalent: yarn compile:fast clean, vitest 138/138. --- .../src/messages/parser/room/unit/RoomUnitParser.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/communication/src/messages/parser/room/unit/RoomUnitParser.ts b/packages/communication/src/messages/parser/room/unit/RoomUnitParser.ts index de703ae..8ac25a6 100644 --- a/packages/communication/src/messages/parser/room/unit/RoomUnitParser.ts +++ b/packages/communication/src/messages/parser/room/unit/RoomUnitParser.ts @@ -146,7 +146,16 @@ export class RoomUnitParser implements IMessageParser user.roomEntryMethod = wrapper.readString(); user.roomEntryTeleportId = wrapper.readInt(); - user.borderId = (wrapper.bytesAvailable ? wrapper.readInt() : 0); + // NOTE: do NOT read borderId here. `wrapper.bytesAvailable` + // is a boolean meaning "any bytes left in the whole packet?", + // not "any bytes left in THIS user". For users that aren't + // the last in the roster, bytesAvailable === true because + // the NEXT user's bytes follow — reading an int would steal + // 4 bytes from the next user and cascade-corrupt the entire + // roster (users not seeing each other on first sight). The + // border id for an individual user arrives via + // RoomUnitInfoParser (single-user packet), where the + // bytesAvailable guard is safe because there is no loop. i++; } From 1bad1b20cae91c861af634d1b753dd2ca8e4dc75 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Tue, 19 May 2026 21:48:02 +0200 Subject: [PATCH 28/30] fix(parser): restore per-user borderId read in RoomUnitParser MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The earlier "drop unsafe borderId read" fix (05ea0db) was based on the assumption that Arcturus did not append a per-user borderId at the end of each user record in RoomUsersComposer. That was true at the time — but the Infostand Borders cherry-pick on the Arcturus side (8f8f568, "feat: Infostand Borders") then added `appendInt(getInfostandBorder())` at the end of EVERY user record (single habbo, habbos collection, single bot=0, bots collection=0). With the cherry-pick applied and the parser still skipping the read, each user record left 4 unconsumed bytes on the wire. The NEXT iteration's `id = wrapper.readInt()` then picked up the previous user's borderId, the rest of the loop interpreted shifted bytes as strings/ints, and the entire roster cascaded into corruption — visible to the user as "I cannot see the other users in the room". The bytesAvailable guard around this read is intentionally NOT re-added: `bytesAvailable` is a boolean meaning "any bytes left in the whole packet?", not "any bytes left for THIS user". With Arcturus guaranteed to ship a borderId for every record (constant 0 for bots), the read must be unconditional to stay wire-aligned. --- .../parser/room/unit/RoomUnitParser.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/communication/src/messages/parser/room/unit/RoomUnitParser.ts b/packages/communication/src/messages/parser/room/unit/RoomUnitParser.ts index 8ac25a6..00ad5cd 100644 --- a/packages/communication/src/messages/parser/room/unit/RoomUnitParser.ts +++ b/packages/communication/src/messages/parser/room/unit/RoomUnitParser.ts @@ -146,16 +146,17 @@ export class RoomUnitParser implements IMessageParser user.roomEntryMethod = wrapper.readString(); user.roomEntryTeleportId = wrapper.readInt(); - // NOTE: do NOT read borderId here. `wrapper.bytesAvailable` - // is a boolean meaning "any bytes left in the whole packet?", - // not "any bytes left in THIS user". For users that aren't - // the last in the roster, bytesAvailable === true because - // the NEXT user's bytes follow — reading an int would steal - // 4 bytes from the next user and cascade-corrupt the entire - // roster (users not seeing each other on first sight). The - // border id for an individual user arrives via - // RoomUnitInfoParser (single-user packet), where the - // bytesAvailable guard is safe because there is no loop. + // Arcturus appends a trailing borderId int per user + // (RoomUsersComposer, after the Infostand Borders feature) + // for every record — habbo, bot, rentable bot — using 0 as + // the constant for the records that have no border. The + // read MUST be unconditional: a bytesAvailable guard would + // be semantically wrong here (the guard answers "any byte + // left in the whole packet?" not "any byte left for THIS + // user"), and skipping the read would leave 4 bytes per + // record and cascade-corrupt every subsequent user in the + // roster. + user.borderId = wrapper.readInt(); i++; } From e897fec56e479e896809772437bd424264a83276 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Tue, 19 May 2026 22:17:51 +0200 Subject: [PATCH 29/30] docs(claude): RoomUnitParser per-user borderId wire contract Document that the per-user borderId int in RoomUsersComposer (Arcturus Infostand Borders) must be read unconditionally inside the per-user loop in RoomUnitParser, never wrapped in a bytesAvailable guard. Calls out the failure modes on both server shapes (emits / doesn't emit) explicitly so the next contributor doesn't re-introduce the guard "for safety" the way it was earlier today. Points future trailing-int additions at the same parser block for documentation. --- CLAUDE.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 1a727f1..f7fe301 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -113,6 +113,35 @@ accepts an optional `allowUnderpass` arg at the end of its parameter list; the server-side `RoomSettingsSaveEvent` reads it under `packet.bytesAvailable() > 0`. +### `RoomUnitParser` per-user `borderId` (Infostand Borders wire contract) + +`RoomUsersComposer` on Arcturus (post `54259f8` / fork commit `8f8f568` +"Infostand Borders") appends `appendInt(getInfostandBorder())` at the +end of EVERY user record — habbo, bot, rentable bot — using `0` as the +constant for records without a real border. To stay wire-aligned with +that, `RoomUnitParser` reads `user.borderId = wrapper.readInt();` +unconditionally inside the per-user loop, after +`roomEntryMethod` / `roomEntryTeleportId`. + +DO NOT wrap this in a `wrapper.bytesAvailable ? readInt() : 0` guard. +`bytesAvailable` is a boolean meaning "any bytes left in the WHOLE +packet?" — not "any bytes left for THIS user". For any non-last user +the guard evaluates `true` (next user's bytes follow) and reads, which +is fine ONLY by coincidence when the server emits borderId per user. +On a server that doesn't emit it, the guard steals 4 bytes from the +next record and cascade-corrupts the whole roster (symptom: users not +seeing each other on room enter). On a server that DOES emit it, +skipping the read leaves 4 unconsumed bytes per record and produces +the same corruption. Both shapes are wrong in a loop; unconditional +read paired with a server contract that always emits is the only +correct combination. + +If you ever need to pair this parser with an older Arcturus that +doesn't emit per-user borderId, the fix is on the server side (add +the cherry-pick), not the parser side. Document any future +trailing-int extension in this same place so the next reader doesn't +re-introduce a bytesAvailable guard "for safety". + ### Dropped dead code: `sendWhisperGroupMessage` `IRoomSession.sendWhisperGroupMessage(userId)` referenced a From c15b606a4d7cd5919817c775037507047002acad Mon Sep 17 00:00:00 2001 From: duckietm Date: Wed, 20 May 2026 11:43:54 +0200 Subject: [PATCH 30/30] =?UTF-8?q?=F0=9F=8C=B2=20Bump=20to=20Version=203.5.?= =?UTF-8?q?0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/utils/src/NitroVersion.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/NitroVersion.ts b/packages/utils/src/NitroVersion.ts index ecd2186..86b85df 100644 --- a/packages/utils/src/NitroVersion.ts +++ b/packages/utils/src/NitroVersion.ts @@ -1,7 +1,7 @@ export class NitroVersion { - public static RENDERER_VERSION: string = '3.0.0'; - public static UI_VERSION: string = '3.0.4'; + public static RENDERER_VERSION: string = '3.5.0'; + public static UI_VERSION: string = '3.5.0'; public static sayHello(): void {