diff --git a/packages/api/src/communication/IConnection.ts b/packages/api/src/communication/IConnection.ts index 3139c22..f9a1837 100644 --- a/packages/api/src/communication/IConnection.ts +++ b/packages/api/src/communication/IConnection.ts @@ -5,6 +5,7 @@ import { IMessageEvent } from './IMessageEvent'; export interface IConnection { init(socketUrl: string): void; + dispose(): void; ready(): void; authenticated(): void; send(...composers: IMessageComposer[]): void; diff --git a/packages/api/src/nitro/session/IRoomSessionManager.ts b/packages/api/src/nitro/session/IRoomSessionManager.ts index 546df80..974c98a 100644 --- a/packages/api/src/nitro/session/IRoomSessionManager.ts +++ b/packages/api/src/nitro/session/IRoomSessionManager.ts @@ -7,5 +7,6 @@ export interface IRoomSessionManager createSession(roomId: number, password?: string): boolean; startSession(session: IRoomSession): boolean; removeSession(id: number, openLandingView?: boolean): void; + tryRestoreSession(): boolean; viewerSession: IRoomSession; } diff --git a/packages/communication/src/CommunicationManager.ts b/packages/communication/src/CommunicationManager.ts index 8acf399..0fa6a90 100644 --- a/packages/communication/src/CommunicationManager.ts +++ b/packages/communication/src/CommunicationManager.ts @@ -1,7 +1,7 @@ import { ICommunicationManager, IConnection, IMessageConfiguration, IMessageEvent } from '@nitrots/api'; import { GetConfiguration } from '@nitrots/configuration'; -import { GetEventDispatcher, NitroEventType } from '@nitrots/events'; -import { GetTickerTime } from '@nitrots/utils'; +import { GetEventDispatcher, NitroEvent, NitroEventType } from '@nitrots/events'; +import { GetTickerTime, NitroLogger } from '@nitrots/utils'; import { NitroMessages } from './NitroMessages'; import { SocketConnection } from './SocketConnection'; import { AuthenticatedEvent, ClientHelloMessageComposer, ClientPingEvent, InfoRetrieveMessageComposer, PongMessageComposer, SSOTicketMessageComposer, UniqueIDMessageComposer } from './messages'; @@ -17,7 +17,11 @@ export class CommunicationManager implements ICommunicationManager private _socketClosedCallback: () => void = null; private _socketOpenedCallback: () => void = null; private _socketErrorCallback: () => void = null; - + private _socketReconnectedCallback: () => void = null; + + private _machineId: string = null; + private _initResolved: boolean = false; + private getGpu(): string { const e = document.createElement('canvas'); let t, s, i, r; @@ -41,7 +45,7 @@ export class CommunicationManager implements ICommunicationManager return 'Error'; } } - + private getCanvas(): any { const e = document.createElement('canvas'), t = e.getContext('2d'), userAgent = navigator.userAgent, screenInfo = '${window.screen.width}x${window.screen.height}', currentDate = new Date().toString(), s = 'ThiosIsVerrySeCuRe02938883721moreStuff! | ${userAgent} | ${screenInfo} | ${currentDate}'; t.textBaseline = 'top'; @@ -67,24 +71,33 @@ export class CommunicationManager implements ICommunicationManager } return r; } - + private generateMachineID(): string { const fp = new ClientJS(); const uniqueId = fp.getCustomFingerprint( - fp.getAvailableResolution(), + fp.getAvailableResolution(), fp.getOS(), - fp.getCPU(), - fp.getColorDepth(), - this.getGpu(), - fp.getSilverlightVersion(), - fp.getOSVersion(), - this.getMathResult(), - fp.getCanvasPrint(), + fp.getCPU(), + fp.getColorDepth(), + this.getGpu(), + fp.getSilverlightVersion(), + fp.getOSVersion(), + this.getMathResult(), + fp.getCanvasPrint(), this.getCanvas() ); return uniqueId == null ? 'FAILED' : `IID-${uniqueId}`; } + private sendHandshake(): void + { + if(!this._machineId) this._machineId = this.generateMachineID(); + + this._connection.send(new ClientHelloMessageComposer(null, null, null, null)); + this._connection.send(new SSOTicketMessageComposer(GetConfiguration().getValue('sso.ticket', null), GetTickerTime())); + this._connection.send(new UniqueIDMessageComposer(this._machineId, '', '')); + } + constructor() { this._connection.registerMessages(this._messages); @@ -99,6 +112,17 @@ export class CommunicationManager implements ICommunicationManager }; GetEventDispatcher().addEventListener(NitroEventType.SOCKET_CLOSED, this._socketClosedCallback); + // Handle reconnection - re-authenticate when socket reconnects + this._socketReconnectedCallback = () => + { + NitroLogger.log('[CommunicationManager] Socket reconnected, re-authenticating...'); + + if(GetConfiguration().getValue('system.pong.manually', false)) this.startPong(); + + this.sendHandshake(); + }; + GetEventDispatcher().addEventListener(NitroEventType.SOCKET_RECONNECTED, this._socketReconnectedCallback); + return new Promise((resolve, reject) => { // Store callback for cleanup @@ -106,18 +130,14 @@ export class CommunicationManager implements ICommunicationManager { if(GetConfiguration().getValue('system.pong.manually', false)) this.startPong(); - const machineId = this.generateMachineID(); - - this._connection.send(new ClientHelloMessageComposer(null, null, null, null)); - this._connection.send(new SSOTicketMessageComposer(GetConfiguration().getValue('sso.ticket', null), GetTickerTime())); - this._connection.send(new UniqueIDMessageComposer(machineId, '', '')); + this.sendHandshake(); }; GetEventDispatcher().addEventListener(NitroEventType.SOCKET_OPENED, this._socketOpenedCallback); // Store callback for cleanup this._socketErrorCallback = () => { - reject(); + if(!this._initResolved) reject(); }; GetEventDispatcher().addEventListener(NitroEventType.SOCKET_ERROR, this._socketErrorCallback); @@ -125,11 +145,30 @@ export class CommunicationManager implements ICommunicationManager const pingEvent = new ClientPingEvent((event: ClientPingEvent) => this.sendPong()); const authEvent = new AuthenticatedEvent((event: AuthenticatedEvent) => { + const isReconnect = this._initResolved; + + NitroLogger.log('[CommunicationManager] AuthenticatedEvent received (isReconnect=' + isReconnect + ')'); + this._connection.authenticated(); - resolve(); + if(!this._initResolved) + { + this._initResolved = true; + resolve(); + } + + if(isReconnect) + { + this._connection.ready(); + } event.connection.send(new InfoRetrieveMessageComposer()); + + if(isReconnect) + { + NitroLogger.log('[CommunicationManager] Dispatching SOCKET_REAUTHENTICATED'); + GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SOCKET_REAUTHENTICATED)); + } }); this._messageEvents.push(pingEvent, authEvent); @@ -164,6 +203,12 @@ export class CommunicationManager implements ICommunicationManager this._socketErrorCallback = null; } + if(this._socketReconnectedCallback) + { + GetEventDispatcher().removeEventListener(NitroEventType.SOCKET_RECONNECTED, this._socketReconnectedCallback); + this._socketReconnectedCallback = null; + } + // Remove message events for(const event of this._messageEvents) { diff --git a/packages/communication/src/NitroMessages.ts b/packages/communication/src/NitroMessages.ts index ec3e632..7d61b36 100644 --- a/packages/communication/src/NitroMessages.ts +++ b/packages/communication/src/NitroMessages.ts @@ -1,6 +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, 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, CanCreateRoomEvent, CanCreateRoomEventEvent, CanCreateRoomMessageComposer, CancelEventMessageComposer, CancelMarketplaceOfferMessageComposer, CancelMysteryBoxWaitMessageEvent, CancelPetBreedingComposer, CancelQuestMessageComposer, CatalogGroupsComposer, CatalogPageExpirationEvent, CatalogPageMessageEvent, CatalogPageWithEarliestExpiryMessageEvent, CatalogPagesListEvent, 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, ConvertGlobalRoomIdMessageComposer, ConvertedRoomIdEvent, CraftComposer, CraftSecretComposer, CraftableProductsEvent, CraftingRecipeEvent, CraftingRecipesAvailableEvent, CraftingResultEvent, CreateFlatMessageComposer, CurrentTimingCodeMessageEvent, CustomUserNotificationMessageEvent, DeclineFriendMessageComposer, DefaultSanctionMessageComposer, DeleteFavouriteRoomMessageComposer, DeleteItemMessageComposer, DeleteBadgeMessageComposer, DeletePetMessageComposer, DeletePendingCallsForHelpMessageComposer, DesktopViewComposer, DesktopViewEvent, DiceValueMessageEvent, DirectSMSClubBuyAvailableMessageEvent, DisconnectMessageComposer, DisconnectReasonEvent, DoorbellMessageEvent, EditEventMessageComposer, ElementPointerMessageEvent, EmailStatusResultEvent, EpicPopupMessageEvent, ExtendRentOrBuyoutFurniMessageComposer, ExtendRentOrBuyoutStripItemMessageComposer, ExtendedProfileChangedMessageEvent, 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, FurniturePickupComposer, FurniturePickupAllComposer, FurniturePlaceComposer, FurniturePlacePaintComposer, FurniturePostItPlaceComposer, FurniturePostItPlacedEvent, FurnitureRandomStateComposer, FurnitureStackHeightComposer, FurnitureStackHeightEvent, FurnitureWallAddEvent, FurnitureWallEvent, FurnitureWallMultiStateComposer, FurnitureWallRemoveEvent, FurnitureWallUpdateComposer, FurnitureWallUpdateEvent, 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, GetForumStatsMessageComposer, GetForumsListMessageComposer, 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, GoToFlatMessageComposer, GotMysteryBoxPrizeMessageEvent, 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, GuideSessionInviteRequesterMessageComposer, GuideSessionInvitedToGuideRoomMessageEvent, 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, IgnoreResultEvent, IgnoreUserComposer, IgnoreUserIdComposer, IgnoredUsersEvent, InClientLinkEvent, IncomingHeader, InfoFeedEnableMessageEvent, InfoRetrieveMessageComposer, InitCameraMessageEvent, InitDiffieHandshakeEvent, InitDiffieHandshakeMessageComposer, InstantMessageErrorEvent, InterstitialMessageEvent, InterstitialShownMessageComposer, IsBadgeRequestFulfilledEvent, IsOfferGiftableMessageEvent, IsUserPartOfCompetitionMessageEvent, IssueCloseNotificationMessageEvent, IssueDeletedMessageEvent, IssueInfoMessageEvent, IssuePickFailedMessageEvent, ItemDataUpdateMessageEvent, JoinQueueMessageComposer, JoinedQueueMessageEvent, JoiningQueueFailedMessageEvent, JukeboxPlayListFullMessageEvent, JukeboxSongDisksMessageEvent, LagWarningReportMessageComposer, LeaveQueueMessageComposer, LeftQueueMessageEvent, LimitedEditionSoldOutEvent, LimitedOfferAppearingNextMessageEvent, LoadGameMessageEvent, LoadGameUrlEvent, LoveLockFurniFinishedEvent, LoveLockFurniFriendConfirmedEvent, LoveLockFurniStartEvent, MOTDNotificationEvent, MaintenanceStatusMessageEvent, MakeOfferMessageComposer, MarkCatalogNewAdditionsPageOpenedComposer, MarketPlaceOffersEvent, MarketplaceBuyOfferResultEvent, MarketplaceCanMakeOfferResult, MarketplaceCancelOfferResultEvent, MarketplaceConfigurationEvent, MarketplaceItemStatsEvent, MarketplaceMakeOfferResult, MarketplaceOwnOffersEvent, MessageErrorEvent, MessengerInitComposer, MessengerInitEvent, MiniMailNewMessageEvent, MiniMailUnreadCountEvent, ModAlertMessageComposer, ModBanMessageComposer, ModKickMessageComposer, ModMessageMessageComposer, ModMuteMessageComposer, ModToolPreferencesComposer, ModToolSanctionComposer, ModTradingLockMessageComposer, ModerateMessageMessageComposer, ModerateRoomMessageComposer, ModerateThreadMessageComposer, ModeratorActionMessageComposer, ModeratorActionResultMessageEvent, ModeratorCautionEvent, ModeratorInitMessageEvent, ModeratorMessageEvent, ModeratorRoomInfoEvent, ModeratorToolPreferencesEvent, ModeratorUserInfoEvent, MoodlightSettingsComposer, MoodlightSettingsSaveComposer, MoodlightTogggleStateComposer, MuteAllInRoomEvent, MyFavouriteRoomsSearchMessageComposer, MyFrequentRoomHistorySearchMessageComposer, MyFriendsRoomsSearchMessageComposer, MyGuildBasesSearchMessageComposer, MyRecommendedRoomsMessageComposer, MyRoomHistorySearchMessageComposer, MyRoomRightsSearchMessageComposer, MyRoomsSearchMessageComposer, MysteryBoxKeysEvent, MysteryBoxWaitingCanceledMessageComposer, NavigatorCategoryListModeComposer, NavigatorCollapsedEvent, NavigatorDeleteSavedSearchComposer, NavigatorHomeRoomEvent, NavigatorInitComposer, NavigatorLiftedEvent, NavigatorMetadataEvent, NavigatorOpenRoomCreatorEvent, NavigatorSearchCloseComposer, NavigatorSearchComposer, NavigatorSearchEvent, NavigatorSearchOpenComposer, NavigatorSearchSaveComposer, NavigatorSearchesEvent, NavigatorSettingsEvent, NavigatorSettingsSaveComposer, NewConsoleMessageEvent, NewFriendRequestEvent, NewUserExperienceGetGiftsComposer, NewUserExperienceGiftOfferMessageEvent, NewUserExperienceNotCompleteEvent, NewUserExperienceScriptProceedComposer, NoOwnedRoomsAlertMessageEvent, NoSuchFlatEvent, NoobnessLevelMessageEvent, 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, PopularRoomTagsResultEvent, PopularRoomsSearchMessageComposer, PostMessageMessageComposer, PostMessageMessageEvent, PostQuizAnswersComposer, PostThreadMessageEvent, PresentOpenedMessageEvent, ProductOfferEvent, PromoArticlesMessageEvent, PublishPhotoMessageComposer, PurchaseBasicMembershipExtensionComposer, PurchaseErrorMessageEvent, PurchaseFromCatalogAsGiftComposer, PurchaseFromCatalogComposer, PurchaseNotAllowedMessageEvent, PurchaseOKMessageEvent, PurchasePhotoMessageComposer, PurchaseRoomAdMessageComposer, PurchaseTargetedOfferComposer, PurchaseVipMembershipExtensionComposer, QuestCancelledMessageEvent, QuestCompletedMessageEvent, QuestDailyMessageEvent, QuestMessageEvent, QuestionAnsweredEvent, QuestionEvent, QuestionFinishedEvent, 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, RoomBanUserComposer, RoomBannedUsersComposer, RoomChatSettingsEvent, RoomChatlogEvent, 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, RoomSettingsSaveErrorEvent, RoomSettingsSavedEvent, RoomSettingsUpdatedEvent, 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, RoomsWhereMyFriendsAreSearchMessageComposer, RoomsWithHighestScoreSearchMessageComposer, SSOTicketMessageComposer, 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, 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 } from './messages'; -import { ClickUserMessageComposer } from './messages'; export class NitroMessages implements IMessageConfiguration { @@ -581,7 +580,6 @@ export class NitroMessages implements IMessageConfiguration { // CUSTOM PACKETS this._composers.set(OutgoingHeader.CLICK_FURNI, ClickFurniMessageComposer); - this._composers.set(OutgoingHeader.CLICK_USER, ClickUserMessageComposer); // AUTHENTICATION this._composers.set(OutgoingHeader.AUTHENTICATION, AuthenticationMessageComposer); diff --git a/packages/communication/src/SocketConnection.ts b/packages/communication/src/SocketConnection.ts index 93d0c15..d252924 100644 --- a/packages/communication/src/SocketConnection.ts +++ b/packages/communication/src/SocketConnection.ts @@ -1,5 +1,5 @@ import { ICodec, IConnection, IMessageComposer, IMessageConfiguration, IMessageDataWrapper, IMessageEvent, WebSocketEventEnum } from '@nitrots/api'; -import { GetEventDispatcher, NitroEvent, NitroEventType } from '@nitrots/events'; +import { GetEventDispatcher, NitroEvent, NitroEventType, ReconnectEvent } from '@nitrots/events'; import { NitroLogger } from '@nitrots/utils'; import { EvaWireFormat } from './codec'; import { MessageClassManager } from './messages'; @@ -23,19 +23,39 @@ export class SocketConnection implements IConnection private _onErrorCallback: (event: Event) => void = null; private _onMessageCallback: (event: MessageEvent) => void = null; + // Reconnection state + private _socketUrl: string = null; + private _reconnectAttempt: number = 0; + private _reconnectTimer: ReturnType = null; + private _isReconnecting: boolean = false; + private _intentionalClose: boolean = false; + private _wasAuthenticated: boolean = false; + + public static readonly MAX_RECONNECT_ATTEMPTS: number = 7; + public static readonly BASE_RECONNECT_DELAY_MS: number = 1000; + public static readonly MAX_RECONNECT_DELAY_MS: number = 30000; + public init(socketUrl: string): void { if(!socketUrl || !socketUrl.length) return; + this._socketUrl = socketUrl; + this._intentionalClose = false; + + this.createSocket(socketUrl); + } + + private createSocket(socketUrl: string): void + { this._dataBuffer = new ArrayBuffer(0); this._socket = new WebSocket(socketUrl); this._socket.binaryType = 'arraybuffer'; // Store callbacks for cleanup - this._onOpenCallback = () => GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SOCKET_OPENED)); - this._onCloseCallback = () => GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SOCKET_CLOSED)); - this._onErrorCallback = () => GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SOCKET_ERROR)); + this._onOpenCallback = () => this.onSocketOpened(); + this._onCloseCallback = (event: Event) => this.onSocketClosed(event as CloseEvent); + this._onErrorCallback = () => this.onSocketError(); this._onMessageCallback = (event: MessageEvent) => { this._dataBuffer = this.concatArrayBuffers(this._dataBuffer, event.data); @@ -48,29 +68,152 @@ export class SocketConnection implements IConnection this._socket.addEventListener(WebSocketEventEnum.CONNECTION_MESSAGE, this._onMessageCallback); } - public dispose(): void + private onSocketOpened(): void { - if(this._socket) + if(this._isReconnecting) { - // Remove all event listeners - if(this._onOpenCallback) this._socket.removeEventListener(WebSocketEventEnum.CONNECTION_OPENED, this._onOpenCallback); - if(this._onCloseCallback) this._socket.removeEventListener(WebSocketEventEnum.CONNECTION_CLOSED, this._onCloseCallback); - if(this._onErrorCallback) this._socket.removeEventListener(WebSocketEventEnum.CONNECTION_ERROR, this._onErrorCallback); - if(this._onMessageCallback) this._socket.removeEventListener(WebSocketEventEnum.CONNECTION_MESSAGE, this._onMessageCallback); + NitroLogger.log('[SocketConnection] Reconnected successfully after ' + this._reconnectAttempt + ' attempt(s)'); - // Close socket if still open - if(this._socket.readyState === WebSocket.OPEN || this._socket.readyState === WebSocket.CONNECTING) - { - this._socket.close(); - } + this._reconnectAttempt = 0; + this._isReconnecting = false; - this._socket = null; + GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SOCKET_RECONNECTED)); + } + else + { + GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SOCKET_OPENED)); + } + } + + private onSocketClosed(event: CloseEvent): void + { + NitroLogger.log('[SocketConnection] Socket closed, code: ' + (event?.code ?? 'unknown') + ', reason: ' + (event?.reason || 'none')); + + if(this._intentionalClose) + { + GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SOCKET_CLOSED)); + return; } + const code = event?.code ?? 0; + + if(code === 1000 || code === 1001) + { + NitroLogger.log('[SocketConnection] Server closed cleanly (code ' + code + ') - not reconnecting'); + + this._isAuthenticated = false; + this._isReady = false; + + GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SOCKET_CLOSED)); + return; + } + + if(this._isAuthenticated) this._wasAuthenticated = true; + + this._isAuthenticated = false; + this._isReady = false; + this._pendingClientMessages = []; + this._pendingServerMessages = []; + + this.attemptReconnect(); + } + + private onSocketError(): void + { + if(this._isReconnecting) + { + NitroLogger.log('[SocketConnection] Reconnect attempt ' + this._reconnectAttempt + ' failed'); + return; + } + + if(!this._wasAuthenticated && !this._isAuthenticated) + { + GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SOCKET_ERROR)); + } + } + + private attemptReconnect(): void + { + if(this._reconnectAttempt >= SocketConnection.MAX_RECONNECT_ATTEMPTS) + { + NitroLogger.log('[SocketConnection] Max reconnect attempts reached (' + SocketConnection.MAX_RECONNECT_ATTEMPTS + ')'); + + this._isReconnecting = false; + this._wasAuthenticated = false; + + GetEventDispatcher().dispatchEvent(new ReconnectEvent( + NitroEventType.SOCKET_RECONNECT_FAILED, + this._reconnectAttempt, + SocketConnection.MAX_RECONNECT_ATTEMPTS + )); + + GetEventDispatcher().dispatchEvent(new NitroEvent(NitroEventType.SOCKET_CLOSED)); + + return; + } + + this._isReconnecting = true; + this._reconnectAttempt++; + + const delay = Math.min( + SocketConnection.BASE_RECONNECT_DELAY_MS * Math.pow(2, this._reconnectAttempt - 1) + Math.random() * 1000, + SocketConnection.MAX_RECONNECT_DELAY_MS + ); + + NitroLogger.log('[SocketConnection] Reconnecting in ' + Math.round(delay) + 'ms (attempt ' + this._reconnectAttempt + '/' + SocketConnection.MAX_RECONNECT_ATTEMPTS + ')'); + + GetEventDispatcher().dispatchEvent(new ReconnectEvent( + NitroEventType.SOCKET_RECONNECTING, + this._reconnectAttempt, + SocketConnection.MAX_RECONNECT_ATTEMPTS + )); + + this._reconnectTimer = setTimeout(() => + { + this._reconnectTimer = null; + + this.cleanupSocket(); + + this.createSocket(this._socketUrl); + }, delay); + } + + private cleanupSocket(): void + { + if(!this._socket) return; + + if(this._onOpenCallback) this._socket.removeEventListener(WebSocketEventEnum.CONNECTION_OPENED, this._onOpenCallback); + if(this._onCloseCallback) this._socket.removeEventListener(WebSocketEventEnum.CONNECTION_CLOSED, this._onCloseCallback); + if(this._onErrorCallback) this._socket.removeEventListener(WebSocketEventEnum.CONNECTION_ERROR, this._onErrorCallback); + if(this._onMessageCallback) this._socket.removeEventListener(WebSocketEventEnum.CONNECTION_MESSAGE, this._onMessageCallback); + + if(this._socket.readyState === WebSocket.OPEN || this._socket.readyState === WebSocket.CONNECTING) + { + try { this._socket.close(); } catch(e) { /* ignore */ } + } + + this._socket = null; this._onOpenCallback = null; this._onCloseCallback = null; this._onErrorCallback = null; this._onMessageCallback = null; + } + + public dispose(): void + { + this._intentionalClose = true; + + if(this._reconnectTimer) + { + clearTimeout(this._reconnectTimer); + this._reconnectTimer = null; + } + + this._isReconnecting = false; + this._reconnectAttempt = 0; + this._wasAuthenticated = false; + + this.cleanupSocket(); this._pendingClientMessages = []; this._pendingServerMessages = []; @@ -142,7 +285,7 @@ export class SocketConnection implements IConnection private write(buffer: ArrayBuffer): void { - if(this._socket.readyState !== WebSocket.OPEN) return; + if(!this._socket || this._socket.readyState !== WebSocket.OPEN) return; this._socket.send(buffer); } @@ -286,6 +429,16 @@ export class SocketConnection implements IConnection return this._isAuthenticated; } + public get isReconnecting(): boolean + { + return this._isReconnecting; + } + + public get wasAuthenticated(): boolean + { + return this._wasAuthenticated; + } + public get dataBuffer(): ArrayBuffer { return this._dataBuffer; diff --git a/packages/communication/src/messages/outgoing/OutgoingHeader.ts b/packages/communication/src/messages/outgoing/OutgoingHeader.ts index f1d9b8f..69f9061 100644 --- a/packages/communication/src/messages/outgoing/OutgoingHeader.ts +++ b/packages/communication/src/messages/outgoing/OutgoingHeader.ts @@ -1,7 +1,6 @@ export class OutgoingHeader { public static CLICK_FURNI = 6002; - public static CLICK_USER = 10020; public static ACHIEVEMENT_LIST = 219; public static AUTHENTICATION = -1; diff --git a/packages/communication/src/messages/outgoing/room/engine/ClickUserMessageComposer.ts b/packages/communication/src/messages/outgoing/room/engine/ClickUserMessageComposer.ts deleted file mode 100644 index a59d16d..0000000 --- a/packages/communication/src/messages/outgoing/room/engine/ClickUserMessageComposer.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { IMessageComposer } from '@nitrots/api'; - -export class ClickUserMessageComposer implements IMessageComposer> -{ - private _data: ConstructorParameters; - - constructor(roomUnitId: number) - { - this._data = [ roomUnitId ]; - } - - public getMessageArray() - { - return this._data; - } - - public dispose(): void - { - return; - } -} diff --git a/packages/communication/src/messages/outgoing/room/engine/index.ts b/packages/communication/src/messages/outgoing/room/engine/index.ts index a3aeb1a..ebee1cd 100644 --- a/packages/communication/src/messages/outgoing/room/engine/index.ts +++ b/packages/communication/src/messages/outgoing/room/engine/index.ts @@ -2,7 +2,6 @@ export * from './BotPlaceComposer'; export * from './BotRemoveComposer'; export * from './BotSkillSaveComposer'; export * from './ClickFurniMessageComposer'; -export * from './ClickUserMessageComposer'; export * from './CompostPlantMessageComposer'; export * from './GetItemDataComposer'; export * from './HarvestPetMessageComposer'; diff --git a/packages/events/src/NitroEventType.ts b/packages/events/src/NitroEventType.ts index 563bcb7..9e575ac 100644 --- a/packages/events/src/NitroEventType.ts +++ b/packages/events/src/NitroEventType.ts @@ -8,6 +8,10 @@ export class NitroEventType public static readonly SOCKET_CLOSED = 'SOCKET_CLOSED'; public static readonly SOCKET_ERROR = 'SOCKET_ERROR'; public static readonly SOCKET_CONNECTED = 'SOCKET_CONNECTED'; + public static readonly SOCKET_RECONNECTING = 'SOCKET_RECONNECTING'; + public static readonly SOCKET_RECONNECTED = 'SOCKET_RECONNECTED'; + public static readonly SOCKET_RECONNECT_FAILED = 'SOCKET_RECONNECT_FAILED'; + public static readonly SOCKET_REAUTHENTICATED = 'SOCKET_REAUTHENTICATED'; public static readonly AVATAR_ASSET_DOWNLOADED = 'AVATAR_ASSET_DOWNLOADED'; public static readonly AVATAR_ASSET_LOADED = 'AVATAR_ASSET_LOADED'; public static readonly AVATAR_EFFECT_DOWNLOADED = 'AVATAR_EFFECT_DOWNLOADED'; diff --git a/packages/events/src/core/ReconnectEvent.ts b/packages/events/src/core/ReconnectEvent.ts new file mode 100644 index 0000000..45ce1c0 --- /dev/null +++ b/packages/events/src/core/ReconnectEvent.ts @@ -0,0 +1,25 @@ +import { NitroEvent } from './NitroEvent'; + +export class ReconnectEvent extends NitroEvent +{ + private _attempt: number; + private _maxAttempts: number; + + constructor(type: string, attempt: number = 0, maxAttempts: number = 0) + { + super(type); + + this._attempt = attempt; + this._maxAttempts = maxAttempts; + } + + public get attempt(): number + { + return this._attempt; + } + + public get maxAttempts(): number + { + return this._maxAttempts; + } +} diff --git a/packages/events/src/core/index.ts b/packages/events/src/core/index.ts index 9a5d80e..97ec3db 100644 --- a/packages/events/src/core/index.ts +++ b/packages/events/src/core/index.ts @@ -1,4 +1,5 @@ export * from './ConfigurationEvent'; export * from './MessageEvent'; export * from './NitroEvent'; +export * from './ReconnectEvent'; export * from './SocketConnectionEvent'; diff --git a/packages/room/src/RoomObjectEventHandler.ts b/packages/room/src/RoomObjectEventHandler.ts index 65ca483..2725f26 100644 --- a/packages/room/src/RoomObjectEventHandler.ts +++ b/packages/room/src/RoomObjectEventHandler.ts @@ -1,5 +1,5 @@ import { IFurnitureStackingHeightMap, ILegacyWallGeometry, IObjectData, IRoomCanvasMouseListener, IRoomEngineServices, IRoomGeometry, IRoomObject, IRoomObjectController, IRoomObjectEventManager, ISelectedRoomObjectData, IVector3D, MouseEventType, RoomObjectCategory, RoomObjectOperationType, RoomObjectPlacementSource, RoomObjectType, RoomObjectUserType, RoomObjectVariable } from '@nitrots/api'; -import { BotPlaceComposer, ClickFurniMessageComposer, ClickUserMessageComposer, FurnitureColorWheelComposer, FurnitureDiceActivateComposer, FurnitureDiceDeactivateComposer, FurnitureFloorUpdateComposer, FurnitureGroupInfoComposer, FurnitureMultiStateComposer, FurnitureOneWayDoorComposer, FurniturePickupComposer, FurniturePlaceComposer, FurniturePostItPlaceComposer, FurnitureRandomStateComposer, FurnitureWallMultiStateComposer, FurnitureWallUpdateComposer, GetCommunication, GetItemDataComposer, GetResolutionAchievementsMessageComposer, PetMoveComposer, PetPlaceComposer, RemoveWallItemComposer, RoomUnitLookComposer, RoomUnitWalkComposer, SetItemDataMessageComposer, SetObjectDataMessageComposer } from '@nitrots/communication'; +import { BotPlaceComposer, ClickFurniMessageComposer, FurnitureColorWheelComposer, FurnitureDiceActivateComposer, FurnitureDiceDeactivateComposer, FurnitureFloorUpdateComposer, FurnitureGroupInfoComposer, FurnitureMultiStateComposer, FurnitureOneWayDoorComposer, FurniturePickupComposer, FurniturePlaceComposer, FurniturePostItPlaceComposer, FurnitureRandomStateComposer, FurnitureWallMultiStateComposer, FurnitureWallUpdateComposer, GetCommunication, GetItemDataComposer, GetResolutionAchievementsMessageComposer, PetMoveComposer, PetPlaceComposer, RemoveWallItemComposer, RoomUnitLookComposer, RoomUnitWalkComposer, SetItemDataMessageComposer, SetObjectDataMessageComposer } from '@nitrots/communication'; import { GetConfiguration } from '@nitrots/configuration'; import { GetEventDispatcher, RoomEngineDimmerStateEvent, RoomEngineObjectEvent, RoomEngineObjectPlacedEvent, RoomEngineObjectPlacedOnUserEvent, RoomEngineObjectPlaySoundEvent, RoomEngineRoomAdEvent, RoomEngineSamplePlaybackEvent, RoomEngineTriggerWidgetEvent, RoomEngineUseProductEvent, RoomObjectBadgeAssetEvent, RoomObjectDataRequestEvent, RoomObjectDimmerStateUpdateEvent, RoomObjectEvent, RoomObjectFloorHoleEvent, RoomObjectFurnitureActionEvent, RoomObjectHSLColorEnableEvent, RoomObjectHSLColorEnabledEvent, RoomObjectMouseEvent, RoomObjectMoveEvent, RoomObjectPlaySoundIdEvent, RoomObjectRoomAdEvent, RoomObjectSamplePlaybackEvent, RoomObjectSoundMachineEvent, RoomObjectStateChangedEvent, RoomObjectTileMouseEvent, RoomObjectWallMouseEvent, RoomObjectWidgetRequestEvent, RoomSpriteMouseEvent } from '@nitrots/events'; import { GetRoomSessionManager, GetSessionDataManager } from '@nitrots/session'; @@ -297,7 +297,7 @@ export class RoomObjectEventHandler implements IRoomCanvasMouseListener, IRoomOb } } - private clickRoomObject(event: RoomObjectMouseEvent, operation: string): void + private clickRoomObject(event: RoomObjectMouseEvent): void { if(!event || event.altKey || event.ctrlKey || event.shiftKey) return; @@ -319,25 +319,20 @@ export class RoomObjectEventHandler implements IRoomCanvasMouseListener, IRoomOb return; } - - if((category === RoomObjectCategory.UNIT) && (operation === RoomObjectOperationType.OBJECT_UNDEFINED) && (objectType === RoomObjectUserType.USER)) - { - GetCommunication().connection.send(new ClickUserMessageComposer(objectId)); - } } private handleRoomObjectMouseClickEvent(event: RoomObjectMouseEvent, roomId: number): void { if(!event) return; + this.clickRoomObject(event); + let operation = RoomObjectOperationType.OBJECT_UNDEFINED; const selectedData = this.getSelectedRoomObjectData(roomId); if(selectedData) operation = selectedData.operation; - this.clickRoomObject(event, operation); - let didWalk = false; let didMove = false; diff --git a/packages/room/src/common/floorplan/FloorplanEditor.ts b/packages/room/src/common/floorplan/FloorplanEditor.ts index 1a5fae8..d6d2db2 100644 --- a/packages/room/src/common/floorplan/FloorplanEditor.ts +++ b/packages/room/src/common/floorplan/FloorplanEditor.ts @@ -25,8 +25,6 @@ export class FloorplanEditor private _image: HTMLImageElement; - public onTilemapChange: (() => void) | null = null; - constructor() { const width = TILE_SIZE * MAX_NUM_TILE_PER_AXIS + 20; @@ -299,8 +297,6 @@ export class FloorplanEditor } this.renderSquareSelectionPreview(); - - if(this.onTilemapChange) this.onTilemapChange(); } private renderSquareSelectionPreview(): void @@ -477,7 +473,6 @@ export class FloorplanEditor this._squareSelectStart = null; this._squareSelectEnd = null; this.clearCanvas(); - this.onTilemapChange = null; } diff --git a/packages/session/src/RoomSessionManager.ts b/packages/session/src/RoomSessionManager.ts index 073b9c8..6584bbe 100644 --- a/packages/session/src/RoomSessionManager.ts +++ b/packages/session/src/RoomSessionManager.ts @@ -1,22 +1,49 @@ import { IRoomHandlerListener, IRoomSession, IRoomSessionManager } from '@nitrots/api'; import { GetCommunication } from '@nitrots/communication'; -import { GetEventDispatcher, RoomSessionEvent } from '@nitrots/events'; +import { GetEventDispatcher, 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'; +const STORAGE_KEY_ROOM_ID = 'nitro.session.lastRoomId'; +const STORAGE_KEY_ROOM_PASSWORD = 'nitro.session.lastRoomPassword'; + export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerListener { private _handlers: BaseHandler[] = []; private _sessions: Map = new Map(); private _pendingSession: IRoomSession = null; - private _sessionStarting: boolean = false; private _viewerSession: IRoomSession = null; + private _lastRoomId: number = -1; + private _lastRoomPassword: string = null; + private _isReconnecting: boolean = false; + private _reconnectGuardTimer: ReturnType = null; public async init(): Promise { this.createHandlers(); this.processPendingSession(); + this.setupReconnectListener(); + this.checkPersistedRoom(); + } + + private checkPersistedRoom(): void + { + try + { + const storedRoomId = sessionStorage.getItem(STORAGE_KEY_ROOM_ID); + + if(!storedRoomId) return; + + const roomId = parseInt(storedRoomId, 10); + + if(isNaN(roomId) || roomId <= 0) return; + this._lastRoomId = roomId; + this._lastRoomPassword = sessionStorage.getItem(STORAGE_KEY_ROOM_PASSWORD) || null; + this._isReconnecting = true; + } + catch(e) {} } private createHandlers(): void @@ -40,6 +67,158 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList ); } + private setupReconnectListener(): void + { + GetEventDispatcher().addEventListener(NitroEventType.SOCKET_RECONNECTING, () => + { + this._isReconnecting = true; + }); + + GetEventDispatcher().addEventListener(NitroEventType.SOCKET_RECONNECTED, () => + { + this.clearGuardTimer(); + this._reconnectGuardTimer = setTimeout(() => + { + this._reconnectGuardTimer = null; + + if(!this._isReconnecting) return; + this.attemptRoomReEntry(); + }, 5000); + }); + + GetEventDispatcher().addEventListener(NitroEventType.SOCKET_REAUTHENTICATED, () => + { + this.clearGuardTimer(); + this.attemptRoomReEntry(); + }); + + GetEventDispatcher().addEventListener(NitroEventType.SOCKET_RECONNECT_FAILED, () => + { + this.clearGuardTimer(); + this._isReconnecting = false; + this._lastRoomId = -1; + this._lastRoomPassword = null; + this.clearPersistedRoom(); + }); + + GetEventDispatcher().addEventListener(NitroEventType.SOCKET_CLOSED, () => + { + this.clearGuardTimer(); + this._isReconnecting = false; + this._lastRoomId = -1; + this._lastRoomPassword = null; + this.clearPersistedRoom(); + }); + } + + private clearGuardTimer(): void + { + if(this._reconnectGuardTimer) + { + clearTimeout(this._reconnectGuardTimer); + this._reconnectGuardTimer = null; + } + } + + private attemptRoomReEntry(): void + { + const roomId = this._lastRoomId; + const password = this._lastRoomPassword; + + if(roomId <= 0) + { + this._isReconnecting = false; + + return; + } + + this._sessions.clear(); + this._viewerSession = null; + this.createSession(roomId, password); + this.clearGuardTimer(); + this._reconnectGuardTimer = setTimeout(() => + { + this._reconnectGuardTimer = null; + + if(this._isReconnecting) + { + this._isReconnecting = false; + } + }, 10000); + } + + public tryRestoreSession(): boolean + { + try + { + const storedRoomId = sessionStorage.getItem(STORAGE_KEY_ROOM_ID); + + if(!storedRoomId) return false; + + const roomId = parseInt(storedRoomId, 10); + + if(isNaN(roomId) || roomId <= 0) return false; + + const password = sessionStorage.getItem(STORAGE_KEY_ROOM_PASSWORD) || null; + + this._isReconnecting = true; + + this.createSession(roomId, password); + + this.clearGuardTimer(); + this._reconnectGuardTimer = setTimeout(() => + { + this._reconnectGuardTimer = null; + + if(this._isReconnecting) + { + this._isReconnecting = false; + } + }, 10000); + + return true; + } + catch(e) + { + return false; + } + } + + private persistRoom(roomId: number, password: string): void + { + try + { + if(roomId > 0) + { + sessionStorage.setItem(STORAGE_KEY_ROOM_ID, roomId.toString()); + + if(password) + { + sessionStorage.setItem(STORAGE_KEY_ROOM_PASSWORD, password); + } + else + { + sessionStorage.removeItem(STORAGE_KEY_ROOM_PASSWORD); + } + } + else + { + this.clearPersistedRoom(); + } + } + catch(e) {} + } + + private clearPersistedRoom(): void + { + try + { + sessionStorage.removeItem(STORAGE_KEY_ROOM_ID); + sessionStorage.removeItem(STORAGE_KEY_ROOM_PASSWORD); + } + catch(e) {} + } + private setHandlers(session: IRoomSession): void { if(!this._handlers || !this._handlers.length) return; @@ -92,6 +271,10 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList this._viewerSession = roomSession; + this._lastRoomId = roomSession.roomId; + this._lastRoomPassword = roomSession.password; + this.persistRoom(roomSession.roomId, roomSession.password); + this.startSession(this._viewerSession); return true; @@ -125,6 +308,18 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList this._sessions.delete(this.getRoomId(id)); + if(openLandingView && !this._isReconnecting) + { + this._lastRoomId = -1; + this._lastRoomPassword = null; + this.clearPersistedRoom(); + } + + if(this._isReconnecting) + { + return; + } + GetEventDispatcher().dispatchEvent(new RoomSessionEvent(RoomSessionEvent.ENDED, session, openLandingView)); } @@ -132,15 +327,34 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList { const session = this.getSession(id); - if(!session) return; + if(!session) + { + return; + } switch(type) { case RoomSessionHandler.RS_CONNECTED: + if(this._isReconnecting) + { + this.clearGuardTimer(); + this._isReconnecting = false; + } + return; case RoomSessionHandler.RS_READY: + + if(this._isReconnecting) + { + this.clearGuardTimer(); + this._isReconnecting = false; + } + return; case RoomSessionHandler.RS_DISCONNECTED: + + if(this._isReconnecting) return; + this.removeSession(id); return; } @@ -158,6 +372,9 @@ export class RoomSessionManager implements IRoomSessionManager, IRoomHandlerList this._sessions.set(this.getRoomId(toRoomId), existing); + this._lastRoomId = toRoomId; + this.persistRoom(toRoomId, existing.password); + this.setHandlers(existing); } diff --git a/packages/session/src/handler/RoomChatHandler.ts b/packages/session/src/handler/RoomChatHandler.ts index 41aa2b0..cc17a8b 100644 --- a/packages/session/src/handler/RoomChatHandler.ts +++ b/packages/session/src/handler/RoomChatHandler.ts @@ -54,7 +54,7 @@ export class RoomChatHandler extends BaseHandler if(!parser) return; - GetEventDispatcher().dispatchEvent(new RoomSessionChatEvent(RoomSessionChatEvent.CHAT_EVENT, session, parser.giverUserId, '', RoomSessionChatEvent.CHAT_TYPE_HAND_ITEM_RECEIVED, SystemChatStyleEnum.GENERIC, [], null, parser.handItemType)); + GetEventDispatcher().dispatchEvent(new RoomSessionChatEvent(RoomSessionChatEvent.CHAT_EVENT, session, parser.giverUserId, '', RoomSessionChatEvent.CHAT_TYPE_HAND_ITEM_RECEIVED, SystemChatStyleEnum.GENERIC, [], parser.handItemType)); } private onRespectReceivedEvent(event: RespectReceivedEvent): void @@ -136,7 +136,7 @@ export class RoomChatHandler extends BaseHandler break; } - GetEventDispatcher().dispatchEvent(new RoomSessionChatEvent(RoomSessionChatEvent.CHAT_EVENT, session, petData.roomIndex, '', chatType, SystemChatStyleEnum.GENERIC, [], null, userRoomIndex)); + GetEventDispatcher().dispatchEvent(new RoomSessionChatEvent(RoomSessionChatEvent.CHAT_EVENT, session, petData.roomIndex, '', chatType, SystemChatStyleEnum.GENERIC, null, userRoomIndex)); } private onFloodControlEvent(event: FloodControlEvent): void @@ -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)); } } diff --git a/packages/utils/src/filters/WiredFilter.ts b/packages/utils/src/filters/WiredFilter.ts index 10bc8d0..303ae86 100644 --- a/packages/utils/src/filters/WiredFilter.ts +++ b/packages/utils/src/filters/WiredFilter.ts @@ -63,14 +63,13 @@ export class WiredFilter extends Filter void main(void) { vec4 currentColor = texture(uTexture, vTextureCoord); - vec3 colorLine = uLineColor; - vec3 colorOverlay = uColor; + vec3 colorLine = uLineColor * currentColor.a; + vec3 colorOverlay = uColor * currentColor.a; if(currentColor.r == 0.0 && currentColor.g == 0.0 && currentColor.b == 0.0 && currentColor.a > 0.0) { finalColor = vec4(colorLine.r, colorLine.g, colorLine.b, currentColor.a); } else if(currentColor.a > 0.0) { - vec3 blendedOverlay = mix(currentColor.rgb, colorOverlay, 0.28); - finalColor = vec4(blendedOverlay.r, blendedOverlay.g, blendedOverlay.b, currentColor.a); + finalColor = vec4(colorOverlay.r, colorOverlay.g, colorOverlay.b, currentColor.a); } } `,