refactor(session): fold permission map into UserPermissionsEvent

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.
This commit is contained in:
simoleo89
2026-05-19 19:39:49 +02:00
parent 159c5eb6e8
commit 221f186d61
8 changed files with 42 additions and 91 deletions
+12 -12
View File
@@ -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();
}