From a599e0cf8927540075b234f206cdcba31eabc9a3 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 18 May 2026 20:50:24 +0200 Subject: [PATCH] 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